github.com/david-imola/snapd@v0.0.0-20210611180407-2de8ddeece6d/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/gadget" 28 "github.com/snapcore/snapd/overlord/assertstate" 29 "github.com/snapcore/snapd/overlord/snapstate" 30 "github.com/snapcore/snapd/overlord/state" 31 "github.com/snapcore/snapd/release" 32 "github.com/snapcore/snapd/snap" 33 "github.com/snapcore/snapd/snap/naming" 34 ) 35 36 func isSameAssertsRevision(err error) bool { 37 if e, ok := err.(*asserts.RevisionError); ok { 38 if e.Used == e.Current { 39 return true 40 } 41 } 42 return false 43 } 44 45 var injectedSetModelError error 46 47 // InjectSetModelError will trigger the selected error in the doSetModel 48 // handler. This is only useful for testing. 49 func InjectSetModelError(err error) { 50 injectedSetModelError = err 51 } 52 53 func (m *DeviceManager) doSetModel(t *state.Task, _ *tomb.Tomb) (err error) { 54 st := t.State() 55 st.Lock() 56 defer st.Unlock() 57 58 remodCtx, err := remodelCtxFromTask(t) 59 if err != nil { 60 return err 61 } 62 new := remodCtx.Model() 63 64 // unmark no-longer required snaps 65 var cleanedRequiredSnaps []string 66 requiredSnaps := getAllRequiredSnapsForModel(new) 67 // TODO|XXX: have AllByRef 68 snapStates, err := snapstate.All(st) 69 if err != nil { 70 return err 71 } 72 for snapName, snapst := range snapStates { 73 // TODO: remove this type restriction once we remodel 74 // gadgets and add tests that ensure 75 // that the required flag is properly set/unset 76 typ, err := snapst.Type() 77 if err != nil { 78 return err 79 } 80 if typ != snap.TypeApp && typ != snap.TypeBase && typ != snap.TypeKernel { 81 continue 82 } 83 // clean required flag if no-longer needed 84 if snapst.Flags.Required && !requiredSnaps.Contains(naming.Snap(snapName)) { 85 snapst.Flags.Required = false 86 snapstate.Set(st, snapName, snapst) 87 cleanedRequiredSnaps = append(cleanedRequiredSnaps, snapName) 88 } 89 // TODO: clean "required" flag of "core" if a remodel 90 // moves from the "core" snap to a different 91 // bootable base snap. 92 } 93 // ensure we undo the cleanedRequiredSnaps if e.g. remodCtx 94 defer func() { 95 if err == nil { 96 return 97 } 98 var snapst snapstate.SnapState 99 for _, snapName := range cleanedRequiredSnaps { 100 if err := snapstate.Get(st, snapName, &snapst); err == nil { 101 snapst.Flags.Required = true 102 snapstate.Set(st, snapName, &snapst) 103 } 104 } 105 }() 106 107 // only useful for testing 108 if injectedSetModelError != nil { 109 return injectedSetModelError 110 } 111 112 // add the assertion only after everything else was successful 113 err = assertstate.Add(st, new) 114 if err != nil && !isSameAssertsRevision(err) { 115 return err 116 } 117 118 // and finish (this will set the new model) 119 return remodCtx.Finish() 120 } 121 122 func (m *DeviceManager) cleanupRemodel(t *state.Task, _ *tomb.Tomb) error { 123 st := t.State() 124 st.Lock() 125 defer st.Unlock() 126 // cleanup the cached remodel context 127 cleanupRemodelCtx(t.Change()) 128 return nil 129 } 130 131 func (m *DeviceManager) doPrepareRemodeling(t *state.Task, tmb *tomb.Tomb) error { 132 st := t.State() 133 st.Lock() 134 defer st.Unlock() 135 136 remodCtx, err := remodelCtxFromTask(t) 137 if err != nil { 138 return err 139 } 140 current, err := findModel(st) 141 if err != nil { 142 return err 143 } 144 145 sto := remodCtx.Store() 146 if sto == nil { 147 return fmt.Errorf("internal error: re-registration remodeling should have built a store") 148 } 149 // ensure a new session accounting for the new brand/model 150 st.Unlock() 151 _, err = sto.EnsureDeviceSession() 152 st.Lock() 153 if err != nil { 154 return fmt.Errorf("cannot get a store session based on the new model assertion: %v", err) 155 } 156 157 chgID := t.Change().ID() 158 159 tss, err := remodelTasks(tmb.Context(nil), st, current, remodCtx.Model(), remodCtx, chgID) 160 if err != nil { 161 return err 162 } 163 164 allTs := state.NewTaskSet() 165 for _, ts := range tss { 166 allTs.AddAll(ts) 167 } 168 snapstate.InjectTasks(t, allTs) 169 170 st.EnsureBefore(0) 171 t.SetStatus(state.DoneStatus) 172 173 return nil 174 } 175 176 var ( 177 gadgetIsCompatible = gadget.IsCompatible 178 ) 179 180 func checkGadgetRemodelCompatible(st *state.State, snapInfo, curInfo *snap.Info, snapf snap.Container, flags snapstate.Flags, deviceCtx snapstate.DeviceContext) error { 181 if release.OnClassic { 182 return nil 183 } 184 if snapInfo.Type() != snap.TypeGadget { 185 // We are only interested in gadget snaps. 186 return nil 187 } 188 if deviceCtx == nil || !deviceCtx.ForRemodeling() { 189 // We are only interesting in a remodeling scenario. 190 return nil 191 } 192 193 if curInfo == nil { 194 // snap isn't installed yet, we are likely remodeling to a new 195 // gadget, identify the old gadget 196 curInfo, _ = snapstate.GadgetInfo(st, deviceCtx.GroundContext()) 197 } 198 if curInfo == nil { 199 return fmt.Errorf("cannot identify the current gadget snap") 200 } 201 202 pendingInfo, err := gadget.ReadInfoFromSnapFile(snapf, deviceCtx.Model()) 203 if err != nil { 204 return fmt.Errorf("cannot read new gadget metadata: %v", err) 205 } 206 207 currentData, err := gadgetDataFromInfo(curInfo, deviceCtx.GroundContext().Model()) 208 if err != nil { 209 return fmt.Errorf("cannot read current gadget metadata: %v", err) 210 } 211 212 if err := gadgetIsCompatible(currentData.Info, pendingInfo); err != nil { 213 return fmt.Errorf("cannot remodel to an incompatible gadget: %v", err) 214 } 215 return nil 216 }