gopkg.in/ubuntu-core/snappy.v0@v0.0.0-20210902073436-25a8614f10a6/overlord/devicestate/firstboot.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2014-2020 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 devicestate 21 22 import ( 23 "errors" 24 "fmt" 25 "sort" 26 27 "github.com/snapcore/snapd/asserts" 28 "github.com/snapcore/snapd/dirs" 29 "github.com/snapcore/snapd/i18n" 30 "github.com/snapcore/snapd/overlord/assertstate" 31 "github.com/snapcore/snapd/overlord/devicestate/internal" 32 "github.com/snapcore/snapd/overlord/snapstate" 33 "github.com/snapcore/snapd/overlord/state" 34 "github.com/snapcore/snapd/release" 35 "github.com/snapcore/snapd/seed" 36 "github.com/snapcore/snapd/snap" 37 "github.com/snapcore/snapd/timings" 38 ) 39 40 var errNothingToDo = errors.New("nothing to do") 41 42 func installSeedSnap(st *state.State, sn *seed.Snap, flags snapstate.Flags) (*state.TaskSet, *snap.Info, error) { 43 if sn.Required { 44 flags.Required = true 45 } 46 if sn.Classic { 47 flags.Classic = true 48 } 49 if sn.DevMode { 50 flags.DevMode = true 51 } 52 53 return snapstate.InstallPath(st, sn.SideInfo, sn.Path, "", sn.Channel, flags) 54 } 55 56 func criticalTaskEdges(ts *state.TaskSet) (beginEdge, beforeHooksEdge, hooksEdge *state.Task, err error) { 57 // we expect all three edges, or none (the latter is the case with config tasksets). 58 beginEdge, err = ts.Edge(snapstate.BeginEdge) 59 if err != nil { 60 return nil, nil, nil, nil 61 } 62 beforeHooksEdge, err = ts.Edge(snapstate.BeforeHooksEdge) 63 if err != nil { 64 return nil, nil, nil, err 65 } 66 hooksEdge, err = ts.Edge(snapstate.HooksEdge) 67 if err != nil { 68 return nil, nil, nil, err 69 } 70 71 return beginEdge, beforeHooksEdge, hooksEdge, nil 72 } 73 74 func markSeededTask(st *state.State) *state.Task { 75 return st.NewTask("mark-seeded", i18n.G("Mark system seeded")) 76 } 77 78 func trivialSeeding(st *state.State) []*state.TaskSet { 79 // give the internal core config a chance to run (even if core is 80 // not used at all we put system configuration there) 81 configTs := snapstate.ConfigureSnap(st, "core", 0) 82 markSeeded := markSeededTask(st) 83 markSeeded.WaitAll(configTs) 84 return []*state.TaskSet{configTs, state.NewTaskSet(markSeeded)} 85 } 86 87 type populateStateFromSeedOptions struct { 88 Label string 89 Mode string 90 Preseed bool 91 } 92 93 func populateStateFromSeedImpl(st *state.State, opts *populateStateFromSeedOptions, tm timings.Measurer) ([]*state.TaskSet, error) { 94 mode := "run" 95 sysLabel := "" 96 preseed := false 97 if opts != nil { 98 if opts.Mode != "" { 99 mode = opts.Mode 100 } 101 sysLabel = opts.Label 102 preseed = opts.Preseed 103 } 104 105 // check that the state is empty 106 var seeded bool 107 err := st.Get("seeded", &seeded) 108 if err != nil && err != state.ErrNoState { 109 return nil, err 110 } 111 if seeded { 112 return nil, fmt.Errorf("cannot populate state: already seeded") 113 } 114 115 var deviceSeed seed.Seed 116 // ack all initial assertions 117 timings.Run(tm, "import-assertions[finish]", "finish importing assertions from seed", func(nested timings.Measurer) { 118 deviceSeed, err = importAssertionsFromSeed(st, sysLabel) 119 }) 120 if err != nil && err != errNothingToDo { 121 return nil, err 122 } 123 124 if err == errNothingToDo { 125 return trivialSeeding(st), nil 126 } 127 128 timings.Run(tm, "load-verified-snap-metadata", "load verified snap metadata from seed", func(nested timings.Measurer) { 129 err = deviceSeed.LoadMeta(nested) 130 }) 131 if release.OnClassic && err == seed.ErrNoMeta { 132 if preseed { 133 return nil, fmt.Errorf("no snaps to preseed") 134 } 135 // on classic it is ok to not seed any snaps 136 return trivialSeeding(st), nil 137 } 138 if err != nil { 139 return nil, err 140 } 141 142 model := deviceSeed.Model() 143 144 essentialSeedSnaps := deviceSeed.EssentialSnaps() 145 seedSnaps, err := deviceSeed.ModeSnaps(mode) 146 if err != nil { 147 return nil, err 148 } 149 150 // optimistically forget the deviceSeed here 151 unloadDeviceSeed(st) 152 153 tsAll := []*state.TaskSet{} 154 configTss := []*state.TaskSet{} 155 156 var lastBeforeHooksTask *state.Task 157 var chainTs func(all []*state.TaskSet, ts *state.TaskSet) []*state.TaskSet 158 159 var preseedDoneTask *state.Task 160 if preseed { 161 preseedDoneTask = st.NewTask("mark-preseeded", i18n.G("Mark system pre-seeded")) 162 } 163 164 chainTsPreseeding := func(all []*state.TaskSet, ts *state.TaskSet) []*state.TaskSet { 165 // mark-preseeded task needs to be inserted between preliminary setup and hook tasks 166 beginTask, beforeHooksTask, hooksTask, err := criticalTaskEdges(ts) 167 if err != nil { 168 // XXX: internal error? 169 panic(err) 170 } 171 // we either have all edges or none 172 if beginTask != nil { 173 // hooks must wait for mark-preseeded 174 hooksTask.WaitFor(preseedDoneTask) 175 if n := len(all); n > 0 { 176 // the first hook of the snap waits for all tasks of previous snap 177 hooksTask.WaitAll(all[n-1]) 178 } 179 if lastBeforeHooksTask != nil { 180 beginTask.WaitFor(lastBeforeHooksTask) 181 } 182 preseedDoneTask.WaitFor(beforeHooksTask) 183 lastBeforeHooksTask = beforeHooksTask 184 } else { 185 n := len(all) 186 // no edges: it is a configure snap taskset for core/gadget/kernel 187 if n != 0 { 188 ts.WaitAll(all[n-1]) 189 } 190 } 191 return append(all, ts) 192 } 193 194 chainTsFullSeeding := func(all []*state.TaskSet, ts *state.TaskSet) []*state.TaskSet { 195 n := len(all) 196 if n != 0 { 197 ts.WaitAll(all[n-1]) 198 } 199 return append(all, ts) 200 } 201 202 if preseed { 203 chainTs = chainTsPreseeding 204 } else { 205 chainTs = chainTsFullSeeding 206 } 207 208 chainSorted := func(infos []*snap.Info, infoToTs map[*snap.Info]*state.TaskSet) { 209 sort.Stable(snap.ByType(infos)) 210 for _, info := range infos { 211 ts := infoToTs[info] 212 tsAll = chainTs(tsAll, ts) 213 } 214 } 215 216 // collected snap infos 217 infos := make([]*snap.Info, 0, len(essentialSeedSnaps)+len(seedSnaps)) 218 219 infoToTs := make(map[*snap.Info]*state.TaskSet, len(essentialSeedSnaps)) 220 221 if len(essentialSeedSnaps) != 0 { 222 // we *always* configure "core" here even if bases are used 223 // for booting. "core" is where the system config lives. 224 configTss = chainTs(configTss, snapstate.ConfigureSnap(st, "core", snapstate.UseConfigDefaults)) 225 } 226 227 for _, seedSnap := range essentialSeedSnaps { 228 flags := snapstate.Flags{ 229 SkipConfigure: true, 230 // The kernel is already there either from ubuntu-image or from "install" 231 // mode so skip extract. 232 SkipKernelExtraction: true, 233 // for dangerous models, allow all devmode snaps 234 // XXX: eventually we may need to allow specific snaps to be devmode for 235 // non-dangerous models, we can do that here since that information will 236 // probably be in the model assertion which we have here 237 ApplySnapDevMode: model.Grade() == asserts.ModelDangerous, 238 } 239 240 ts, info, err := installSeedSnap(st, seedSnap, flags) 241 if err != nil { 242 return nil, err 243 } 244 if info.Type() == snap.TypeKernel || info.Type() == snap.TypeGadget { 245 configTs := snapstate.ConfigureSnap(st, info.SnapName(), snapstate.UseConfigDefaults) 246 // wait for the previous configTss 247 configTss = chainTs(configTss, configTs) 248 } 249 infos = append(infos, info) 250 infoToTs[info] = ts 251 } 252 // now add/chain the tasksets in the right order based on essential 253 // snap types 254 chainSorted(infos, infoToTs) 255 256 // chain together configuring core, kernel, and gadget after 257 // installing them so that defaults are availabble from gadget 258 if len(configTss) > 0 { 259 if preseed { 260 configTss[0].WaitFor(preseedDoneTask) 261 } 262 configTss[0].WaitAll(tsAll[len(tsAll)-1]) 263 tsAll = append(tsAll, configTss...) 264 } 265 266 // ensure we install in the right order 267 infoToTs = make(map[*snap.Info]*state.TaskSet, len(seedSnaps)) 268 269 for _, seedSnap := range seedSnaps { 270 flags := snapstate.Flags{ 271 // for dangerous models, allow all devmode snaps 272 // XXX: eventually we may need to allow specific snaps to be devmode for 273 // non-dangerous models, we can do that here since that information will 274 // probably be in the model assertion which we have here 275 ApplySnapDevMode: model.Grade() == asserts.ModelDangerous, 276 } 277 278 ts, info, err := installSeedSnap(st, seedSnap, flags) 279 if err != nil { 280 return nil, err 281 } 282 infos = append(infos, info) 283 infoToTs[info] = ts 284 } 285 286 // validate that all snaps have bases 287 errs := snap.ValidateBasesAndProviders(infos) 288 if errs != nil { 289 // only report the first error encountered 290 return nil, errs[0] 291 } 292 293 // now add/chain the tasksets in the right order, note that we 294 // only have tasksets that we did not already seeded 295 chainSorted(infos[len(essentialSeedSnaps):], infoToTs) 296 297 if len(tsAll) == 0 { 298 return nil, fmt.Errorf("cannot proceed, no snaps to seed") 299 } 300 301 // ts is the taskset of the last snap 302 ts := tsAll[len(tsAll)-1] 303 endTs := state.NewTaskSet() 304 305 markSeeded := markSeededTask(st) 306 if preseed { 307 endTs.AddTask(preseedDoneTask) 308 markSeeded.WaitFor(preseedDoneTask) 309 } 310 whatSeeds := &seededSystem{ 311 System: sysLabel, 312 Model: model.Model(), 313 BrandID: model.BrandID(), 314 Revision: model.Revision(), 315 Timestamp: model.Timestamp(), 316 } 317 markSeeded.Set("seed-system", whatSeeds) 318 319 // mark-seeded waits for the taskset of last snap 320 markSeeded.WaitAll(ts) 321 endTs.AddTask(markSeeded) 322 tsAll = append(tsAll, endTs) 323 324 return tsAll, nil 325 } 326 327 func importAssertionsFromSeed(st *state.State, sysLabel string) (seed.Seed, error) { 328 // TODO: use some kind of context fo Device/SetDevice? 329 device, err := internal.Device(st) 330 if err != nil { 331 return nil, err 332 } 333 334 // collect and 335 // set device,model from the model assertion 336 deviceSeed, err := loadDeviceSeed(st, sysLabel) 337 if err == seed.ErrNoAssertions && release.OnClassic { 338 // on classic seeding is optional 339 // set the fallback model 340 err := setClassicFallbackModel(st, device) 341 if err != nil { 342 return nil, err 343 } 344 return nil, errNothingToDo 345 } 346 if err != nil { 347 return nil, err 348 } 349 modelAssertion := deviceSeed.Model() 350 351 classicModel := modelAssertion.Classic() 352 if release.OnClassic != classicModel { 353 var msg string 354 if classicModel { 355 msg = "cannot seed an all-snaps system with a classic model" 356 } else { 357 msg = "cannot seed a classic system with an all-snaps model" 358 } 359 return nil, fmt.Errorf(msg) 360 } 361 362 // set device,model from the model assertion 363 if err := setDeviceFromModelAssertion(st, device, modelAssertion); err != nil { 364 return nil, err 365 } 366 367 return deviceSeed, nil 368 } 369 370 // loadDeviceSeed loads and caches the device seed based on sysLabel, 371 // it is meant to be used before and during seeding. 372 // It is an error to call it with different sysLabel values once one 373 // seed has been loaded and cached. 374 func loadDeviceSeed(st *state.State, sysLabel string) (deviceSeed seed.Seed, err error) { 375 cached := st.Cached(loadedDeviceSeedKey{}) 376 if cached != nil { 377 loaded := cached.(*loadedDeviceSeed) 378 if loaded.sysLabel != sysLabel { 379 return nil, fmt.Errorf("internal error: requested inconsistent device seed: %s (was %s)", sysLabel, loaded.sysLabel) 380 } 381 return loaded.seed, loaded.err 382 } 383 384 // cache the outcome, both success and errors 385 defer func() { 386 st.Cache(loadedDeviceSeedKey{}, &loadedDeviceSeed{ 387 sysLabel: sysLabel, 388 seed: deviceSeed, 389 err: err, 390 }) 391 }() 392 393 deviceSeed, err = seed.Open(dirs.SnapSeedDir, sysLabel) 394 if err != nil { 395 return nil, err 396 } 397 398 // collect and 399 // set device,model from the model assertion 400 commitTo := func(batch *asserts.Batch) error { 401 return assertstate.AddBatch(st, batch, nil) 402 } 403 404 if err := deviceSeed.LoadAssertions(assertstate.DB(st), commitTo); err != nil { 405 return nil, err 406 } 407 408 return deviceSeed, nil 409 } 410 411 // unloadDeviceSeed forgets the cached outcomes of loadDeviceSeed. 412 // Its main reason is to avoid using memory past the point where the deviceSeed 413 // isn't needed anymore. 414 func unloadDeviceSeed(st *state.State) { 415 st.Cache(loadedDeviceSeedKey{}, nil) 416 } 417 418 type loadedDeviceSeedKey struct{} 419 type loadedDeviceSeed struct { 420 sysLabel string 421 seed seed.Seed 422 err error 423 }