github.com/chipaca/snappy@v0.0.0-20210104084008-1f06296fe8ad/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 deviceSeed, err := seed.Open(dirs.SnapSeedDir, sysLabel) 116 if err != nil { 117 return nil, err 118 } 119 120 var model *asserts.Model 121 // ack all initial assertions 122 timings.Run(tm, "import-assertions", "import assertions from seed", func(nested timings.Measurer) { 123 model, err = importAssertionsFromSeed(st, deviceSeed) 124 }) 125 if err != nil && err != errNothingToDo { 126 return nil, err 127 } 128 129 if err == errNothingToDo { 130 return trivialSeeding(st), nil 131 } 132 133 err = deviceSeed.LoadMeta(tm) 134 if release.OnClassic && err == seed.ErrNoMeta { 135 if preseed { 136 return nil, fmt.Errorf("no snaps to preseed") 137 } 138 // on classic it is ok to not seed any snaps 139 return trivialSeeding(st), nil 140 } 141 if err != nil { 142 return nil, err 143 } 144 145 essentialSeedSnaps := deviceSeed.EssentialSnaps() 146 seedSnaps, err := deviceSeed.ModeSnaps(mode) 147 if err != nil { 148 return nil, err 149 } 150 151 tsAll := []*state.TaskSet{} 152 configTss := []*state.TaskSet{} 153 154 var lastBeforeHooksTask *state.Task 155 var chainTs func(all []*state.TaskSet, ts *state.TaskSet) []*state.TaskSet 156 157 var preseedDoneTask *state.Task 158 if preseed { 159 preseedDoneTask = st.NewTask("mark-preseeded", i18n.G("Mark system pre-seeded")) 160 } 161 162 chainTsPreseeding := func(all []*state.TaskSet, ts *state.TaskSet) []*state.TaskSet { 163 // mark-preseeded task needs to be inserted between preliminary setup and hook tasks 164 beginTask, beforeHooksTask, hooksTask, err := criticalTaskEdges(ts) 165 if err != nil { 166 // XXX: internal error? 167 panic(err) 168 } 169 // we either have all edges or none 170 if beginTask != nil { 171 // hooks must wait for mark-preseeded 172 hooksTask.WaitFor(preseedDoneTask) 173 if n := len(all); n > 0 { 174 // the first hook of the snap waits for all tasks of previous snap 175 hooksTask.WaitAll(all[n-1]) 176 } 177 if lastBeforeHooksTask != nil { 178 beginTask.WaitFor(lastBeforeHooksTask) 179 } 180 preseedDoneTask.WaitFor(beforeHooksTask) 181 lastBeforeHooksTask = beforeHooksTask 182 } else { 183 n := len(all) 184 // no edges: it is a configure snap taskset for core/gadget/kernel 185 if n != 0 { 186 ts.WaitAll(all[n-1]) 187 } 188 } 189 return append(all, ts) 190 } 191 192 chainTsFullSeeding := func(all []*state.TaskSet, ts *state.TaskSet) []*state.TaskSet { 193 n := len(all) 194 if n != 0 { 195 ts.WaitAll(all[n-1]) 196 } 197 return append(all, ts) 198 } 199 200 if preseed { 201 chainTs = chainTsPreseeding 202 } else { 203 chainTs = chainTsFullSeeding 204 } 205 206 chainSorted := func(infos []*snap.Info, infoToTs map[*snap.Info]*state.TaskSet) { 207 sort.Stable(snap.ByType(infos)) 208 for _, info := range infos { 209 ts := infoToTs[info] 210 tsAll = chainTs(tsAll, ts) 211 } 212 } 213 214 // collected snap infos 215 infos := make([]*snap.Info, 0, len(essentialSeedSnaps)+len(seedSnaps)) 216 217 infoToTs := make(map[*snap.Info]*state.TaskSet, len(essentialSeedSnaps)) 218 219 if len(essentialSeedSnaps) != 0 { 220 // we *always* configure "core" here even if bases are used 221 // for booting. "core" is where the system config lives. 222 configTss = chainTs(configTss, snapstate.ConfigureSnap(st, "core", snapstate.UseConfigDefaults)) 223 } 224 225 for _, seedSnap := range essentialSeedSnaps { 226 flags := snapstate.Flags{ 227 SkipConfigure: true, 228 // for dangerous models, allow all devmode snaps 229 // XXX: eventually we may need to allow specific snaps to be devmode for 230 // non-dangerous models, we can do that here since that information will 231 // probably be in the model assertion which we have here 232 ApplySnapDevMode: model.Grade() == asserts.ModelDangerous, 233 } 234 235 ts, info, err := installSeedSnap(st, seedSnap, flags) 236 if err != nil { 237 return nil, err 238 } 239 if info.Type() == snap.TypeKernel || info.Type() == snap.TypeGadget { 240 configTs := snapstate.ConfigureSnap(st, info.SnapName(), snapstate.UseConfigDefaults) 241 // wait for the previous configTss 242 configTss = chainTs(configTss, configTs) 243 } 244 infos = append(infos, info) 245 infoToTs[info] = ts 246 } 247 // now add/chain the tasksets in the right order based on essential 248 // snap types 249 chainSorted(infos, infoToTs) 250 251 // chain together configuring core, kernel, and gadget after 252 // installing them so that defaults are availabble from gadget 253 if len(configTss) > 0 { 254 if preseed { 255 configTss[0].WaitFor(preseedDoneTask) 256 } 257 configTss[0].WaitAll(tsAll[len(tsAll)-1]) 258 tsAll = append(tsAll, configTss...) 259 } 260 261 // ensure we install in the right order 262 infoToTs = make(map[*snap.Info]*state.TaskSet, len(seedSnaps)) 263 264 for _, seedSnap := range seedSnaps { 265 flags := snapstate.Flags{ 266 // for dangerous models, allow all devmode snaps 267 // XXX: eventually we may need to allow specific snaps to be devmode for 268 // non-dangerous models, we can do that here since that information will 269 // probably be in the model assertion which we have here 270 ApplySnapDevMode: model.Grade() == asserts.ModelDangerous, 271 } 272 273 ts, info, err := installSeedSnap(st, seedSnap, flags) 274 if err != nil { 275 return nil, err 276 } 277 infos = append(infos, info) 278 infoToTs[info] = ts 279 } 280 281 // validate that all snaps have bases 282 errs := snap.ValidateBasesAndProviders(infos) 283 if errs != nil { 284 // only report the first error encountered 285 return nil, errs[0] 286 } 287 288 // now add/chain the tasksets in the right order, note that we 289 // only have tasksets that we did not already seeded 290 chainSorted(infos[len(essentialSeedSnaps):], infoToTs) 291 292 if len(tsAll) == 0 { 293 return nil, fmt.Errorf("cannot proceed, no snaps to seed") 294 } 295 296 // ts is the taskset of the last snap 297 ts := tsAll[len(tsAll)-1] 298 endTs := state.NewTaskSet() 299 300 markSeeded := markSeededTask(st) 301 if preseed { 302 endTs.AddTask(preseedDoneTask) 303 markSeeded.WaitFor(preseedDoneTask) 304 } 305 whatSeeds := &seededSystem{ 306 System: sysLabel, 307 Model: model.Model(), 308 BrandID: model.BrandID(), 309 Revision: model.Revision(), 310 Timestamp: model.Timestamp(), 311 } 312 markSeeded.Set("seed-system", whatSeeds) 313 314 // mark-seeded waits for the taskset of last snap 315 markSeeded.WaitAll(ts) 316 endTs.AddTask(markSeeded) 317 tsAll = append(tsAll, endTs) 318 319 return tsAll, nil 320 } 321 322 func importAssertionsFromSeed(st *state.State, deviceSeed seed.Seed) (*asserts.Model, error) { 323 // TODO: use some kind of context fo Device/SetDevice? 324 device, err := internal.Device(st) 325 if err != nil { 326 return nil, err 327 } 328 329 // collect and 330 // set device,model from the model assertion 331 commitTo := func(batch *asserts.Batch) error { 332 return assertstate.AddBatch(st, batch, nil) 333 } 334 335 err = deviceSeed.LoadAssertions(assertstate.DB(st), commitTo) 336 if err == seed.ErrNoAssertions && release.OnClassic { 337 // on classic seeding is optional 338 // set the fallback model 339 err := setClassicFallbackModel(st, device) 340 if err != nil { 341 return nil, err 342 } 343 return nil, errNothingToDo 344 } 345 if err != nil { 346 return nil, err 347 } 348 modelAssertion := deviceSeed.Model() 349 350 classicModel := modelAssertion.Classic() 351 if release.OnClassic != classicModel { 352 var msg string 353 if classicModel { 354 msg = "cannot seed an all-snaps system with a classic model" 355 } else { 356 msg = "cannot seed a classic system with an all-snaps model" 357 } 358 return nil, fmt.Errorf(msg) 359 } 360 361 // set device,model from the model assertion 362 if err := setDeviceFromModelAssertion(st, device, modelAssertion); err != nil { 363 return nil, err 364 } 365 366 return modelAssertion, nil 367 }