github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/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 if new.Grade() != asserts.ModelGradeUnset { 119 // TODO: update ubuntu-boot/device/model 120 // TODO: reseal for both the old and current model 121 } 122 123 // and finish (this will set the new model) 124 return remodCtx.Finish() 125 } 126 127 func (m *DeviceManager) cleanupRemodel(t *state.Task, _ *tomb.Tomb) error { 128 st := t.State() 129 st.Lock() 130 defer st.Unlock() 131 // cleanup the cached remodel context 132 cleanupRemodelCtx(t.Change()) 133 return nil 134 } 135 136 func (m *DeviceManager) doPrepareRemodeling(t *state.Task, tmb *tomb.Tomb) error { 137 st := t.State() 138 st.Lock() 139 defer st.Unlock() 140 141 remodCtx, err := remodelCtxFromTask(t) 142 if err != nil { 143 return err 144 } 145 current, err := findModel(st) 146 if err != nil { 147 return err 148 } 149 150 sto := remodCtx.Store() 151 if sto == nil { 152 return fmt.Errorf("internal error: re-registration remodeling should have built a store") 153 } 154 // ensure a new session accounting for the new brand/model 155 st.Unlock() 156 _, err = sto.EnsureDeviceSession() 157 st.Lock() 158 if err != nil { 159 return fmt.Errorf("cannot get a store session based on the new model assertion: %v", err) 160 } 161 162 chgID := t.Change().ID() 163 164 tss, err := remodelTasks(tmb.Context(nil), st, current, remodCtx.Model(), remodCtx, chgID) 165 if err != nil { 166 return err 167 } 168 169 allTs := state.NewTaskSet() 170 for _, ts := range tss { 171 allTs.AddAll(ts) 172 } 173 snapstate.InjectTasks(t, allTs) 174 175 st.EnsureBefore(0) 176 t.SetStatus(state.DoneStatus) 177 178 return nil 179 } 180 181 var ( 182 gadgetIsCompatible = gadget.IsCompatible 183 ) 184 185 func checkGadgetRemodelCompatible(st *state.State, snapInfo, curInfo *snap.Info, snapf snap.Container, flags snapstate.Flags, deviceCtx snapstate.DeviceContext) error { 186 if release.OnClassic { 187 return nil 188 } 189 if snapInfo.Type() != snap.TypeGadget { 190 // We are only interested in gadget snaps. 191 return nil 192 } 193 if deviceCtx == nil || !deviceCtx.ForRemodeling() { 194 // We are only interesting in a remodeling scenario. 195 return nil 196 } 197 198 if curInfo == nil { 199 // snap isn't installed yet, we are likely remodeling to a new 200 // gadget, identify the old gadget 201 curInfo, _ = snapstate.GadgetInfo(st, deviceCtx.GroundContext()) 202 } 203 if curInfo == nil { 204 return fmt.Errorf("cannot identify the current gadget snap") 205 } 206 207 pendingInfo, err := gadget.ReadInfoFromSnapFile(snapf, deviceCtx.Model()) 208 if err != nil { 209 return fmt.Errorf("cannot read new gadget metadata: %v", err) 210 } 211 212 currentData, err := gadgetDataFromInfo(curInfo, deviceCtx.GroundContext().Model()) 213 if err != nil { 214 return fmt.Errorf("cannot read current gadget metadata: %v", err) 215 } 216 217 if err := gadgetIsCompatible(currentData.Info, pendingInfo); err != nil { 218 return fmt.Errorf("cannot remodel to an incompatible gadget: %v", err) 219 } 220 return nil 221 }