github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/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 lastBeforeHooksTask != nil { 174 beginTask.WaitFor(lastBeforeHooksTask) 175 } 176 preseedDoneTask.WaitFor(beforeHooksTask) 177 lastBeforeHooksTask = beforeHooksTask 178 } else { 179 n := len(all) 180 // no edges: it is a configure snap taskset for core/gadget/kernel 181 if n != 0 { 182 ts.WaitAll(all[n-1]) 183 } 184 } 185 return append(all, ts) 186 } 187 188 chainTsFullSeeding := func(all []*state.TaskSet, ts *state.TaskSet) []*state.TaskSet { 189 n := len(all) 190 if n != 0 { 191 ts.WaitAll(all[n-1]) 192 } 193 return append(all, ts) 194 } 195 196 if preseed { 197 chainTs = chainTsPreseeding 198 } else { 199 chainTs = chainTsFullSeeding 200 } 201 202 chainSorted := func(infos []*snap.Info, infoToTs map[*snap.Info]*state.TaskSet) { 203 sort.Stable(snap.ByType(infos)) 204 for _, info := range infos { 205 ts := infoToTs[info] 206 tsAll = chainTs(tsAll, ts) 207 } 208 } 209 210 // collected snap infos 211 infos := make([]*snap.Info, 0, len(essentialSeedSnaps)+len(seedSnaps)) 212 213 infoToTs := make(map[*snap.Info]*state.TaskSet, len(essentialSeedSnaps)) 214 215 if len(essentialSeedSnaps) != 0 { 216 // we *always* configure "core" here even if bases are used 217 // for booting. "core" is where the system config lives. 218 configTss = chainTs(configTss, snapstate.ConfigureSnap(st, "core", snapstate.UseConfigDefaults)) 219 } 220 221 for _, seedSnap := range essentialSeedSnaps { 222 ts, info, err := installSeedSnap(st, seedSnap, snapstate.Flags{SkipConfigure: true}) 223 if err != nil { 224 return nil, err 225 } 226 if info.Type() == snap.TypeKernel || info.Type() == snap.TypeGadget { 227 configTs := snapstate.ConfigureSnap(st, info.SnapName(), snapstate.UseConfigDefaults) 228 // wait for the previous configTss 229 configTss = chainTs(configTss, configTs) 230 } 231 infos = append(infos, info) 232 infoToTs[info] = ts 233 } 234 // now add/chain the tasksets in the right order based on essential 235 // snap types 236 chainSorted(infos, infoToTs) 237 238 // chain together configuring core, kernel, and gadget after 239 // installing them so that defaults are availabble from gadget 240 if len(configTss) > 0 { 241 if preseed { 242 configTss[0].WaitFor(preseedDoneTask) 243 } 244 configTss[0].WaitAll(tsAll[len(tsAll)-1]) 245 tsAll = append(tsAll, configTss...) 246 } 247 248 // ensure we install in the right order 249 infoToTs = make(map[*snap.Info]*state.TaskSet, len(seedSnaps)) 250 251 for _, seedSnap := range seedSnaps { 252 var flags snapstate.Flags 253 ts, info, err := installSeedSnap(st, seedSnap, flags) 254 if err != nil { 255 return nil, err 256 } 257 infos = append(infos, info) 258 infoToTs[info] = ts 259 } 260 261 // validate that all snaps have bases 262 errs := snap.ValidateBasesAndProviders(infos) 263 if errs != nil { 264 // only report the first error encountered 265 return nil, errs[0] 266 } 267 268 // now add/chain the tasksets in the right order, note that we 269 // only have tasksets that we did not already seeded 270 chainSorted(infos[len(essentialSeedSnaps):], infoToTs) 271 272 if len(tsAll) == 0 { 273 return nil, fmt.Errorf("cannot proceed, no snaps to seed") 274 } 275 276 ts := tsAll[len(tsAll)-1] 277 endTs := state.NewTaskSet() 278 279 markSeeded := markSeededTask(st) 280 if preseed { 281 endTs.AddTask(preseedDoneTask) 282 markSeeded.WaitFor(preseedDoneTask) 283 } 284 whatSeeds := &seededSystem{ 285 System: sysLabel, 286 Model: model.Model(), 287 BrandID: model.BrandID(), 288 Revision: model.Revision(), 289 Timestamp: model.Timestamp(), 290 } 291 markSeeded.Set("seed-system", whatSeeds) 292 293 markSeeded.WaitAll(ts) 294 endTs.AddTask(markSeeded) 295 tsAll = append(tsAll, endTs) 296 297 return tsAll, nil 298 } 299 300 func importAssertionsFromSeed(st *state.State, deviceSeed seed.Seed) (*asserts.Model, error) { 301 // TODO: use some kind of context fo Device/SetDevice? 302 device, err := internal.Device(st) 303 if err != nil { 304 return nil, err 305 } 306 307 // collect and 308 // set device,model from the model assertion 309 commitTo := func(batch *asserts.Batch) error { 310 return assertstate.AddBatch(st, batch, nil) 311 } 312 313 err = deviceSeed.LoadAssertions(assertstate.DB(st), commitTo) 314 if err == seed.ErrNoAssertions && release.OnClassic { 315 // on classic seeding is optional 316 // set the fallback model 317 err := setClassicFallbackModel(st, device) 318 if err != nil { 319 return nil, err 320 } 321 return nil, errNothingToDo 322 } 323 if err != nil { 324 return nil, err 325 } 326 modelAssertion := deviceSeed.Model() 327 328 classicModel := modelAssertion.Classic() 329 if release.OnClassic != classicModel { 330 var msg string 331 if classicModel { 332 msg = "cannot seed an all-snaps system with a classic model" 333 } else { 334 msg = "cannot seed a classic system with an all-snaps model" 335 } 336 return nil, fmt.Errorf(msg) 337 } 338 339 // set device,model from the model assertion 340 if err := setDeviceFromModelAssertion(st, device, modelAssertion); err != nil { 341 return nil, err 342 } 343 344 return modelAssertion, nil 345 }