github.com/david-imola/snapd@v0.0.0-20210611180407-2de8ddeece6d/overlord/devicestate/handlers_gadget.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 "os" 24 "path/filepath" 25 26 "gopkg.in/tomb.v2" 27 28 "github.com/snapcore/snapd/asserts" 29 "github.com/snapcore/snapd/boot" 30 "github.com/snapcore/snapd/dirs" 31 "github.com/snapcore/snapd/gadget" 32 "github.com/snapcore/snapd/logger" 33 "github.com/snapcore/snapd/overlord/snapstate" 34 "github.com/snapcore/snapd/overlord/state" 35 "github.com/snapcore/snapd/release" 36 "github.com/snapcore/snapd/snap" 37 ) 38 39 func makeRollbackDir(name string) (string, error) { 40 rollbackDir := filepath.Join(dirs.SnapRollbackDir, name) 41 42 if err := os.MkdirAll(rollbackDir, 0750); err != nil { 43 return "", err 44 } 45 46 return rollbackDir, nil 47 } 48 49 func currentGadgetInfo(st *state.State, curDeviceCtx snapstate.DeviceContext) (*gadget.GadgetData, error) { 50 currentInfo, err := snapstate.GadgetInfo(st, curDeviceCtx) 51 if err != nil && err != state.ErrNoState { 52 return nil, err 53 } 54 if currentInfo == nil { 55 // no current yet 56 return nil, nil 57 } 58 59 ci, err := gadgetDataFromInfo(currentInfo, curDeviceCtx.Model()) 60 if err != nil { 61 return nil, fmt.Errorf("cannot read current gadget snap details: %v", err) 62 } 63 return ci, nil 64 } 65 66 func pendingGadgetInfo(snapsup *snapstate.SnapSetup, pendingDeviceCtx snapstate.DeviceContext) (*gadget.GadgetData, error) { 67 info, err := snap.ReadInfo(snapsup.InstanceName(), snapsup.SideInfo) 68 if err != nil { 69 return nil, fmt.Errorf("cannot read candidate gadget snap details: %v", err) 70 } 71 72 gi, err := gadgetDataFromInfo(info, pendingDeviceCtx.Model()) 73 if err != nil { 74 return nil, fmt.Errorf("cannot read candidate snap gadget metadata: %v", err) 75 } 76 return gi, nil 77 } 78 79 var ( 80 gadgetUpdate = gadget.Update 81 ) 82 83 func (m *DeviceManager) doUpdateGadgetAssets(t *state.Task, _ *tomb.Tomb) error { 84 if release.OnClassic { 85 return fmt.Errorf("cannot run update gadget assets task on a classic system") 86 } 87 88 st := t.State() 89 st.Lock() 90 defer st.Unlock() 91 92 snapsup, err := snapstate.TaskSnapSetup(t) 93 if err != nil { 94 return err 95 } 96 97 remodelCtx, err := DeviceCtx(st, t, nil) 98 if err != nil { 99 return err 100 } 101 isRemodel := remodelCtx.ForRemodeling() 102 groundDeviceCtx := remodelCtx.GroundContext() 103 104 model := groundDeviceCtx.Model() 105 if isRemodel { 106 model = remodelCtx.Model() 107 } 108 // be extra paranoid when checking we are installing the right gadget 109 var updateData *gadget.GadgetData 110 switch snapsup.Type { 111 case snap.TypeGadget: 112 expectedGadgetSnap := model.Gadget() 113 if snapsup.InstanceName() != expectedGadgetSnap { 114 return fmt.Errorf("cannot apply gadget assets update from non-model gadget snap %q, expected %q snap", 115 snapsup.InstanceName(), expectedGadgetSnap) 116 } 117 118 updateData, err = pendingGadgetInfo(snapsup, remodelCtx) 119 if err != nil { 120 return err 121 } 122 case snap.TypeKernel: 123 expectedKernelSnap := model.Kernel() 124 if snapsup.InstanceName() != expectedKernelSnap { 125 return fmt.Errorf("cannot apply kernel assets update from non-model kernel snap %q, expected %q snap", 126 snapsup.InstanceName(), expectedKernelSnap) 127 } 128 129 // now calculate the "update" data, it's the same gadget but 130 // argumented from a different kernel 131 updateData, err = currentGadgetInfo(t.State(), groundDeviceCtx) 132 if err != nil { 133 return err 134 } 135 default: 136 return fmt.Errorf("internal errror: doUpdateGadgetAssets called with snap type %v", snapsup.Type) 137 } 138 139 currentData, err := currentGadgetInfo(t.State(), groundDeviceCtx) 140 if err != nil { 141 return err 142 } 143 if currentData == nil { 144 // no updates during first boot & seeding 145 return nil 146 } 147 148 // add kernel directories 149 currentKernelInfo, err := snapstate.CurrentInfo(st, groundDeviceCtx.Model().Kernel()) 150 // XXX: switch to the normal `if err != nil { return err }` pattern 151 // here once all tests are updated and have a kernel 152 if err == nil { 153 currentData.KernelRootDir = currentKernelInfo.MountDir() 154 updateData.KernelRootDir = currentKernelInfo.MountDir() 155 } 156 // if this is a gadget update triggered by an updated kernel we 157 // need to ensure "updateData.KernelRootDir" points to the new kernel 158 if snapsup.Type == snap.TypeKernel { 159 updateKernelInfo, err := snap.ReadInfo(snapsup.InstanceName(), snapsup.SideInfo) 160 if err != nil { 161 return fmt.Errorf("cannot read candidate kernel snap details: %v", err) 162 } 163 updateData.KernelRootDir = updateKernelInfo.MountDir() 164 } 165 166 snapRollbackDir, err := makeRollbackDir(fmt.Sprintf("%v_%v", snapsup.InstanceName(), snapsup.SideInfo.Revision)) 167 if err != nil { 168 return fmt.Errorf("cannot prepare update rollback directory: %v", err) 169 } 170 171 var updatePolicy gadget.UpdatePolicyFunc = nil 172 173 // Even with a remodel a kernel refresh only updates the kernel assets 174 if snapsup.Type == snap.TypeKernel { 175 updatePolicy = gadget.KernelUpdatePolicy 176 } else if isRemodel { 177 // use the remodel policy which triggers an update of all 178 // structures 179 updatePolicy = gadget.RemodelUpdatePolicy 180 } 181 182 var updateObserver gadget.ContentUpdateObserver 183 observeTrustedBootAssets, err := boot.TrustedAssetsUpdateObserverForModel(model, updateData.RootDir) 184 if err != nil && err != boot.ErrObserverNotApplicable { 185 return fmt.Errorf("cannot setup asset update observer: %v", err) 186 } 187 if err == nil { 188 updateObserver = observeTrustedBootAssets 189 } 190 // do not release the state lock, the update observer may attempt to 191 // modify modeenv inside, which implicitly is guarded by the state lock; 192 // on top of that we do not expect the update to be moving large amounts 193 // of data 194 err = gadgetUpdate(*currentData, *updateData, snapRollbackDir, updatePolicy, updateObserver) 195 if err != nil { 196 if err == gadget.ErrNoUpdate { 197 // no update needed 198 t.Logf("No gadget assets update needed") 199 return nil 200 } 201 return err 202 } 203 204 t.SetStatus(state.DoneStatus) 205 206 if err := os.RemoveAll(snapRollbackDir); err != nil && !os.IsNotExist(err) { 207 logger.Noticef("failed to remove gadget update rollback directory %q: %v", snapRollbackDir, err) 208 } 209 210 // TODO: consider having the option to do this early via recovery in 211 // core20, have fallback code as well there 212 st.RequestRestart(state.RestartSystem) 213 214 return nil 215 } 216 217 func (m *DeviceManager) updateGadgetCommandLine(t *state.Task, st *state.State, isUndo bool) (updated bool, err error) { 218 snapsup, err := snapstate.TaskSnapSetup(t) 219 if err != nil { 220 return false, err 221 } 222 devCtx, err := DeviceCtx(st, t, nil) 223 if err != nil { 224 return false, err 225 } 226 if devCtx.Model().Grade() == asserts.ModelGradeUnset { 227 // pre UC20 system, do nothing 228 return false, nil 229 } 230 var gadgetData *gadget.GadgetData 231 if !isUndo { 232 // when updating, command line comes from the new gadget 233 gadgetData, err = pendingGadgetInfo(snapsup, devCtx) 234 } else { 235 // but when undoing, we use the current gadget which should have 236 // been restored 237 currentGadgetData, err := currentGadgetInfo(st, devCtx) 238 if err != nil { 239 return false, err 240 } 241 gadgetData = currentGadgetData 242 } 243 updated, err = boot.UpdateCommandLineForGadgetComponent(devCtx, gadgetData.RootDir) 244 if err != nil { 245 return false, fmt.Errorf("cannot update kernel command line from gadget: %v", err) 246 } 247 return updated, nil 248 } 249 250 func (m *DeviceManager) doUpdateGadgetCommandLine(t *state.Task, _ *tomb.Tomb) error { 251 if release.OnClassic { 252 return fmt.Errorf("internal error: cannot run update gadget kernel command line task on a classic system") 253 } 254 255 st := t.State() 256 st.Lock() 257 defer st.Unlock() 258 259 var seeded bool 260 err := st.Get("seeded", &seeded) 261 if err != nil && err != state.ErrNoState { 262 return err 263 } 264 if !seeded { 265 // do nothing during first boot & seeding 266 return nil 267 } 268 269 const isUndo = false 270 updated, err := m.updateGadgetCommandLine(t, st, isUndo) 271 if err != nil { 272 return err 273 } 274 if !updated { 275 logger.Debugf("no kernel command line update from gadget") 276 return nil 277 } 278 t.Logf("Updated kernel command line") 279 280 t.SetStatus(state.DoneStatus) 281 282 // TODO: consider optimization to avoid double reboot when the gadget 283 // snap carries an update to the gadget assets and a change in the 284 // kernel command line 285 286 // kernel command line was updated, request a reboot to make it effective 287 st.RequestRestart(state.RestartSystem) 288 return nil 289 } 290 291 func (m *DeviceManager) undoUpdateGadgetCommandLine(t *state.Task, _ *tomb.Tomb) error { 292 if release.OnClassic { 293 return fmt.Errorf("internal error: cannot run update gadget kernel command line task on a classic system") 294 } 295 296 st := t.State() 297 st.Lock() 298 defer st.Unlock() 299 300 var seeded bool 301 err := st.Get("seeded", &seeded) 302 if err != nil && err != state.ErrNoState { 303 return err 304 } 305 if !seeded { 306 // do nothing during first boot & seeding 307 return nil 308 } 309 310 const isUndo = true 311 updated, err := m.updateGadgetCommandLine(t, st, isUndo) 312 if err != nil { 313 return err 314 } 315 if !updated { 316 logger.Debugf("no kernel command line update to undo") 317 return nil 318 } 319 t.Logf("Reverted kernel command line change") 320 321 t.SetStatus(state.UndoneStatus) 322 323 // kernel command line was updated, request a reboot to make it effective 324 st.RequestRestart(state.RestartSystem) 325 return nil 326 }