github.com/david-imola/snapd@v0.0.0-20210611180407-2de8ddeece6d/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 // for dangerous models, allow all devmode snaps 231 // XXX: eventually we may need to allow specific snaps to be devmode for 232 // non-dangerous models, we can do that here since that information will 233 // probably be in the model assertion which we have here 234 ApplySnapDevMode: model.Grade() == asserts.ModelDangerous, 235 } 236 237 ts, info, err := installSeedSnap(st, seedSnap, flags) 238 if err != nil { 239 return nil, err 240 } 241 if info.Type() == snap.TypeKernel || info.Type() == snap.TypeGadget { 242 configTs := snapstate.ConfigureSnap(st, info.SnapName(), snapstate.UseConfigDefaults) 243 // wait for the previous configTss 244 configTss = chainTs(configTss, configTs) 245 } 246 infos = append(infos, info) 247 infoToTs[info] = ts 248 } 249 // now add/chain the tasksets in the right order based on essential 250 // snap types 251 chainSorted(infos, infoToTs) 252 253 // chain together configuring core, kernel, and gadget after 254 // installing them so that defaults are availabble from gadget 255 if len(configTss) > 0 { 256 if preseed { 257 configTss[0].WaitFor(preseedDoneTask) 258 } 259 configTss[0].WaitAll(tsAll[len(tsAll)-1]) 260 tsAll = append(tsAll, configTss...) 261 } 262 263 // ensure we install in the right order 264 infoToTs = make(map[*snap.Info]*state.TaskSet, len(seedSnaps)) 265 266 for _, seedSnap := range seedSnaps { 267 flags := snapstate.Flags{ 268 // for dangerous models, allow all devmode snaps 269 // XXX: eventually we may need to allow specific snaps to be devmode for 270 // non-dangerous models, we can do that here since that information will 271 // probably be in the model assertion which we have here 272 ApplySnapDevMode: model.Grade() == asserts.ModelDangerous, 273 } 274 275 ts, info, err := installSeedSnap(st, seedSnap, flags) 276 if err != nil { 277 return nil, err 278 } 279 infos = append(infos, info) 280 infoToTs[info] = ts 281 } 282 283 // validate that all snaps have bases 284 errs := snap.ValidateBasesAndProviders(infos) 285 if errs != nil { 286 // only report the first error encountered 287 return nil, errs[0] 288 } 289 290 // now add/chain the tasksets in the right order, note that we 291 // only have tasksets that we did not already seeded 292 chainSorted(infos[len(essentialSeedSnaps):], infoToTs) 293 294 if len(tsAll) == 0 { 295 return nil, fmt.Errorf("cannot proceed, no snaps to seed") 296 } 297 298 // ts is the taskset of the last snap 299 ts := tsAll[len(tsAll)-1] 300 endTs := state.NewTaskSet() 301 302 markSeeded := markSeededTask(st) 303 if preseed { 304 endTs.AddTask(preseedDoneTask) 305 markSeeded.WaitFor(preseedDoneTask) 306 } 307 whatSeeds := &seededSystem{ 308 System: sysLabel, 309 Model: model.Model(), 310 BrandID: model.BrandID(), 311 Revision: model.Revision(), 312 Timestamp: model.Timestamp(), 313 } 314 markSeeded.Set("seed-system", whatSeeds) 315 316 // mark-seeded waits for the taskset of last snap 317 markSeeded.WaitAll(ts) 318 endTs.AddTask(markSeeded) 319 tsAll = append(tsAll, endTs) 320 321 return tsAll, nil 322 } 323 324 func importAssertionsFromSeed(st *state.State, sysLabel string) (seed.Seed, error) { 325 // TODO: use some kind of context fo Device/SetDevice? 326 device, err := internal.Device(st) 327 if err != nil { 328 return nil, err 329 } 330 331 // collect and 332 // set device,model from the model assertion 333 deviceSeed, err := loadDeviceSeed(st, sysLabel) 334 if err == seed.ErrNoAssertions && release.OnClassic { 335 // on classic seeding is optional 336 // set the fallback model 337 err := setClassicFallbackModel(st, device) 338 if err != nil { 339 return nil, err 340 } 341 return nil, errNothingToDo 342 } 343 if err != nil { 344 return nil, err 345 } 346 modelAssertion := deviceSeed.Model() 347 348 classicModel := modelAssertion.Classic() 349 if release.OnClassic != classicModel { 350 var msg string 351 if classicModel { 352 msg = "cannot seed an all-snaps system with a classic model" 353 } else { 354 msg = "cannot seed a classic system with an all-snaps model" 355 } 356 return nil, fmt.Errorf(msg) 357 } 358 359 // set device,model from the model assertion 360 if err := setDeviceFromModelAssertion(st, device, modelAssertion); err != nil { 361 return nil, err 362 } 363 364 return deviceSeed, nil 365 } 366 367 // loadDeviceSeed loads and caches the device seed based on sysLabel, 368 // it is meant to be used before and during seeding. 369 // It is an error to call it with different sysLabel values once one 370 // seed has been loaded and cached. 371 func loadDeviceSeed(st *state.State, sysLabel string) (deviceSeed seed.Seed, err error) { 372 cached := st.Cached(loadedDeviceSeedKey{}) 373 if cached != nil { 374 loaded := cached.(*loadedDeviceSeed) 375 if loaded.sysLabel != sysLabel { 376 return nil, fmt.Errorf("internal error: requested inconsistent device seed: %s (was %s)", sysLabel, loaded.sysLabel) 377 } 378 return loaded.seed, loaded.err 379 } 380 381 // cache the outcome, both success and errors 382 defer func() { 383 st.Cache(loadedDeviceSeedKey{}, &loadedDeviceSeed{ 384 sysLabel: sysLabel, 385 seed: deviceSeed, 386 err: err, 387 }) 388 }() 389 390 deviceSeed, err = seed.Open(dirs.SnapSeedDir, sysLabel) 391 if err != nil { 392 return nil, err 393 } 394 395 // collect and 396 // set device,model from the model assertion 397 commitTo := func(batch *asserts.Batch) error { 398 return assertstate.AddBatch(st, batch, nil) 399 } 400 401 if err := deviceSeed.LoadAssertions(assertstate.DB(st), commitTo); err != nil { 402 return nil, err 403 } 404 405 return deviceSeed, nil 406 } 407 408 // unloadDeviceSeed forgets the cached outcomes of loadDeviceSeed. 409 // Its main reason is to avoid using memory past the point where the deviceSeed 410 // isn't needed anymore. 411 func unloadDeviceSeed(st *state.State) { 412 st.Cache(loadedDeviceSeedKey{}, nil) 413 } 414 415 type loadedDeviceSeedKey struct{} 416 type loadedDeviceSeed struct { 417 sysLabel string 418 seed seed.Seed 419 err error 420 }