github.com/rigado/snapd@v2.42.5-go-mod+incompatible/overlord/devicestate/devicestate.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2016-2019 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 implements the manager and state aspects responsible 21 // for the device identity and policies. 22 package devicestate 23 24 import ( 25 "context" 26 "fmt" 27 "sync" 28 29 "github.com/snapcore/snapd/asserts" 30 "github.com/snapcore/snapd/i18n" 31 "github.com/snapcore/snapd/logger" 32 "github.com/snapcore/snapd/netutil" 33 "github.com/snapcore/snapd/overlord/assertstate" 34 "github.com/snapcore/snapd/overlord/auth" 35 "github.com/snapcore/snapd/overlord/configstate/config" 36 "github.com/snapcore/snapd/overlord/devicestate/internal" 37 "github.com/snapcore/snapd/overlord/ifacestate/ifacerepo" 38 "github.com/snapcore/snapd/overlord/snapstate" 39 "github.com/snapcore/snapd/overlord/state" 40 "github.com/snapcore/snapd/release" 41 "github.com/snapcore/snapd/snap" 42 "github.com/snapcore/snapd/snap/naming" 43 ) 44 45 var ( 46 snapstateInstallWithDeviceContext = snapstate.InstallWithDeviceContext 47 snapstateUpdateWithDeviceContext = snapstate.UpdateWithDeviceContext 48 ) 49 50 // findModel returns the device model assertion. 51 func findModel(st *state.State) (*asserts.Model, error) { 52 device, err := internal.Device(st) 53 if err != nil { 54 return nil, err 55 } 56 57 if device.Brand == "" || device.Model == "" { 58 return nil, state.ErrNoState 59 } 60 61 a, err := assertstate.DB(st).Find(asserts.ModelType, map[string]string{ 62 "series": release.Series, 63 "brand-id": device.Brand, 64 "model": device.Model, 65 }) 66 if asserts.IsNotFound(err) { 67 return nil, state.ErrNoState 68 } 69 if err != nil { 70 return nil, err 71 } 72 73 return a.(*asserts.Model), nil 74 } 75 76 // findSerial returns the device serial assertion. device is optional and used instead of the global state if provided. 77 func findSerial(st *state.State, device *auth.DeviceState) (*asserts.Serial, error) { 78 if device == nil { 79 var err error 80 device, err = internal.Device(st) 81 if err != nil { 82 return nil, err 83 } 84 } 85 86 if device.Serial == "" { 87 return nil, state.ErrNoState 88 } 89 90 a, err := assertstate.DB(st).Find(asserts.SerialType, map[string]string{ 91 "brand-id": device.Brand, 92 "model": device.Model, 93 "serial": device.Serial, 94 }) 95 if asserts.IsNotFound(err) { 96 return nil, state.ErrNoState 97 } 98 if err != nil { 99 return nil, err 100 } 101 102 return a.(*asserts.Serial), nil 103 } 104 105 // auto-refresh 106 func canAutoRefresh(st *state.State) (bool, error) { 107 // we need to be seeded first 108 var seeded bool 109 st.Get("seeded", &seeded) 110 if !seeded { 111 return false, nil 112 } 113 114 // Either we have a serial or we try anyway if we attempted 115 // for a while to get a serial, this would allow us to at 116 // least upgrade core if that can help. 117 if ensureOperationalAttempts(st) >= 3 { 118 return true, nil 119 } 120 121 // Check model exists, for sanity. We always have a model, either 122 // seeded or a generic one that ships with snapd. 123 _, err := findModel(st) 124 if err == state.ErrNoState { 125 return false, nil 126 } 127 if err != nil { 128 return false, err 129 } 130 131 _, err = findSerial(st, nil) 132 if err == state.ErrNoState { 133 return false, nil 134 } 135 if err != nil { 136 return false, err 137 } 138 139 return true, nil 140 } 141 142 func checkGadgetOrKernel(st *state.State, snapInfo, curInfo *snap.Info, flags snapstate.Flags, deviceCtx snapstate.DeviceContext) error { 143 kind := "" 144 var snapType snap.Type 145 var getName func(*asserts.Model) string 146 switch snapInfo.GetType() { 147 case snap.TypeGadget: 148 kind = "gadget" 149 snapType = snap.TypeGadget 150 getName = (*asserts.Model).Gadget 151 case snap.TypeKernel: 152 if release.OnClassic { 153 return fmt.Errorf("cannot install a kernel snap on classic") 154 } 155 156 kind = "kernel" 157 snapType = snap.TypeKernel 158 getName = (*asserts.Model).Kernel 159 default: 160 // not a relevant check 161 return nil 162 } 163 164 model := deviceCtx.Model() 165 166 if snapInfo.SnapID != "" { 167 snapDecl, err := assertstate.SnapDeclaration(st, snapInfo.SnapID) 168 if err != nil { 169 return fmt.Errorf("internal error: cannot find snap declaration for %q: %v", snapInfo.InstanceName(), err) 170 } 171 publisher := snapDecl.PublisherID() 172 if publisher != "canonical" && publisher != model.BrandID() { 173 return fmt.Errorf("cannot install %s %q published by %q for model by %q", kind, snapInfo.InstanceName(), publisher, model.BrandID()) 174 } 175 } else { 176 logger.Noticef("installing unasserted %s %q", kind, snapInfo.InstanceName()) 177 } 178 179 found, err := snapstate.HasSnapOfType(st, snapType) 180 if err != nil { 181 return fmt.Errorf("cannot detect original %s snap: %v", kind, err) 182 } 183 if found { 184 // already installed, snapstate takes care 185 return nil 186 } 187 // first installation of a gadget/kernel 188 189 expectedName := getName(model) 190 if expectedName == "" { // can happen only on classic 191 return fmt.Errorf("cannot install %s snap on classic if not requested by the model", kind) 192 } 193 194 if snapInfo.InstanceName() != snapInfo.SnapName() { 195 return fmt.Errorf("cannot install %q, parallel installation of kernel or gadget snaps is not supported", snapInfo.InstanceName()) 196 } 197 198 if snapInfo.InstanceName() != expectedName { 199 return fmt.Errorf("cannot install %s %q, model assertion requests %q", kind, snapInfo.InstanceName(), expectedName) 200 } 201 202 return nil 203 } 204 205 var once sync.Once 206 207 func delayedCrossMgrInit() { 208 once.Do(func() { 209 snapstate.AddCheckSnapCallback(checkGadgetOrKernel) 210 }) 211 snapstate.CanAutoRefresh = canAutoRefresh 212 snapstate.CanManageRefreshes = CanManageRefreshes 213 snapstate.IsOnMeteredConnection = netutil.IsOnMeteredConnection 214 snapstate.DeviceCtx = DeviceCtx 215 snapstate.Remodeling = Remodeling 216 } 217 218 // proxyStore returns the store assertion for the proxy store if one is set. 219 func proxyStore(st *state.State, tr *config.Transaction) (*asserts.Store, error) { 220 var proxyStore string 221 err := tr.GetMaybe("core", "proxy.store", &proxyStore) 222 if err != nil { 223 return nil, err 224 } 225 if proxyStore == "" { 226 return nil, state.ErrNoState 227 } 228 229 a, err := assertstate.DB(st).Find(asserts.StoreType, map[string]string{ 230 "store": proxyStore, 231 }) 232 if asserts.IsNotFound(err) { 233 return nil, state.ErrNoState 234 } 235 if err != nil { 236 return nil, err 237 } 238 239 return a.(*asserts.Store), nil 240 } 241 242 // interfaceConnected returns true if the given snap/interface names 243 // are connected 244 func interfaceConnected(st *state.State, snapName, ifName string) bool { 245 conns, err := ifacerepo.Get(st).Connected(snapName, ifName) 246 return err == nil && len(conns) > 0 247 } 248 249 // CanManageRefreshes returns true if the device can be 250 // switched to the "core.refresh.schedule=managed" mode. 251 // 252 // TODO: 253 // - Move the CanManageRefreshes code into the ifstate 254 // - Look at the connections and find the connection for snapd-control 255 // with the managed attribute 256 // - Take the snap from this connection and look at the snapstate to see 257 // if that snap has a snap declaration (to ensure it comes from the store) 258 func CanManageRefreshes(st *state.State) bool { 259 snapStates, err := snapstate.All(st) 260 if err != nil { 261 return false 262 } 263 for _, snapst := range snapStates { 264 // Always get the current info even if the snap is currently 265 // being operated on or if its disabled. 266 info, err := snapst.CurrentInfo() 267 if err != nil { 268 continue 269 } 270 if info.Broken != "" { 271 continue 272 } 273 // The snap must have a snap declaration (implies that 274 // its from the store) 275 if _, err := assertstate.SnapDeclaration(st, info.SideInfo.SnapID); err != nil { 276 continue 277 } 278 279 for _, plugInfo := range info.Plugs { 280 if plugInfo.Interface == "snapd-control" && plugInfo.Attrs["refresh-schedule"] == "managed" { 281 snapName := info.InstanceName() 282 plugName := plugInfo.Name 283 if interfaceConnected(st, snapName, plugName) { 284 return true 285 } 286 } 287 } 288 } 289 290 return false 291 } 292 293 func getAllRequiredSnapsForModel(model *asserts.Model) *naming.SnapSet { 294 reqSnaps := model.RequiredWithEssentialSnaps() 295 return naming.NewSnapSet(reqSnaps) 296 } 297 298 // extractDownloadInstallEdgesFromTs extracts the first, last download 299 // phase and install phase tasks from a TaskSet 300 func extractDownloadInstallEdgesFromTs(ts *state.TaskSet) (firstDl, lastDl, firstInst, lastInst *state.Task, err error) { 301 edgeTask, err := ts.Edge(snapstate.DownloadAndChecksDoneEdge) 302 if err != nil { 303 return nil, nil, nil, nil, err 304 } 305 tasks := ts.Tasks() 306 // we know we always start with downloads 307 firstDl = tasks[0] 308 // and always end with installs 309 lastInst = tasks[len(tasks)-1] 310 311 var edgeTaskIndex int 312 for i, task := range tasks { 313 if task == edgeTask { 314 edgeTaskIndex = i 315 break 316 } 317 } 318 return firstDl, tasks[edgeTaskIndex], tasks[edgeTaskIndex+1], lastInst, nil 319 } 320 321 func remodelTasks(ctx context.Context, st *state.State, current, new *asserts.Model, deviceCtx snapstate.DeviceContext, fromChange string) ([]*state.TaskSet, error) { 322 userID := 0 323 var tss []*state.TaskSet 324 325 // adjust kernel track 326 if current.Kernel() == new.Kernel() && current.KernelTrack() != new.KernelTrack() { 327 ts, err := snapstateUpdateWithDeviceContext(st, new.Kernel(), &snapstate.RevisionOptions{Channel: new.KernelTrack()}, userID, snapstate.Flags{NoReRefresh: true}, deviceCtx, fromChange) 328 if err != nil { 329 return nil, err 330 } 331 tss = append(tss, ts) 332 } 333 // add new kernel 334 if current.Kernel() != new.Kernel() { 335 // TODO: we need to support corner cases here like: 336 // 0. start with "old-kernel" 337 // 1. remodel to "new-kernel" 338 // 2. remodel back to "old-kernel" 339 // In step (2) we will get a "already-installed" error 340 // here right now (workaround: remove "old-kernel") 341 ts, err := snapstateInstallWithDeviceContext(ctx, st, new.Kernel(), &snapstate.RevisionOptions{Channel: new.KernelTrack()}, userID, snapstate.Flags{}, deviceCtx, fromChange) 342 if err != nil { 343 return nil, err 344 } 345 tss = append(tss, ts) 346 } 347 348 // add new required-snaps, no longer required snaps will be cleaned 349 // in "set-model" 350 for _, snapRef := range new.RequiredNoEssentialSnaps() { 351 // TODO|XXX: have methods that take refs directly 352 // to respect the snap ids 353 _, err := snapstate.CurrentInfo(st, snapRef.SnapName()) 354 // If the snap is not installed we need to install it now. 355 if _, ok := err.(*snap.NotInstalledError); ok { 356 ts, err := snapstateInstallWithDeviceContext(ctx, st, snapRef.SnapName(), nil, userID, snapstate.Flags{Required: true}, deviceCtx, fromChange) 357 if err != nil { 358 return nil, err 359 } 360 tss = append(tss, ts) 361 } else if err != nil { 362 return nil, err 363 } 364 } 365 // TODO: Validate that all bases and default-providers are part 366 // of the install tasksets and error if not. If the 367 // prereq task handler check starts adding installs into 368 // our remodel change our carefully constructed wait chain 369 // breaks down. 370 371 // Ensure all download/check tasks are run *before* the install 372 // tasks. During a remodel the network may not be available so 373 // we need to ensure we have everything local. 374 var lastDownloadInChain, firstInstallInChain *state.Task 375 var prevDownload, prevInstall *state.Task 376 for _, ts := range tss { 377 // make sure all things happen sequentially 378 // Terminology 379 // A <- B means B waits for A 380 // "download,verify" are part of the "Download" phase 381 // "link,start" is part of "Install" phase 382 // 383 // - all tasks inside ts{Download,Install} already wait for 384 // each other so the chains look something like this: 385 // download1 <- verify1 <- install1 386 // download2 <- verify2 <- install2 387 // download3 <- verify3 <- install3 388 // - add wait of each first ts{Download,Install} task for 389 // the last previous ts{Download,Install} task 390 // Our chains now looks like: 391 // download1 <- verify1 <- install1 (as before) 392 // download2 <- verify2 <- install2 (as before) 393 // download3 <- verify3 <- install3 (as before) 394 // verify1 <- download2 (added) 395 // verify2 <- download3 (added) 396 // install1 <- install2 (added) 397 // install2 <- install3 (added) 398 downloadStart, downloadLast, installFirst, installLast, err := extractDownloadInstallEdgesFromTs(ts) 399 if err != nil { 400 return nil, fmt.Errorf("cannot remodel: %v", err) 401 } 402 if prevDownload != nil { 403 // XXX: we don't strictly need to serialize the download 404 downloadStart.WaitFor(prevDownload) 405 } 406 if prevInstall != nil { 407 installFirst.WaitFor(prevInstall) 408 } 409 prevDownload = downloadLast 410 prevInstall = installLast 411 // update global state 412 lastDownloadInChain = downloadLast 413 if firstInstallInChain == nil { 414 firstInstallInChain = installFirst 415 } 416 } 417 // Make sure the first install waits for the last download. With this 418 // our (simplified) wait chain looks like: 419 // download1 <- verify1 <- download2 <- verify2 <- download3 <- verify3 <- install1 <- install2 <- install3 420 if firstInstallInChain != nil && lastDownloadInChain != nil { 421 firstInstallInChain.WaitFor(lastDownloadInChain) 422 } 423 424 // Set the new model assertion - this *must* be the last thing done 425 // by the change. 426 setModel := st.NewTask("set-model", i18n.G("Set new model assertion")) 427 for _, tsPrev := range tss { 428 setModel.WaitAll(tsPrev) 429 } 430 tss = append(tss, state.NewTaskSet(setModel)) 431 432 return tss, nil 433 } 434 435 // Remodel takes a new model assertion and generates a change that 436 // takes the device from the old to the new model or an error if the 437 // transition is not possible. 438 // 439 // TODO: 440 // - Check estimated disk size delta 441 // - Reapply gadget connections as needed 442 // - Check all relevant snaps exist in new store 443 // (need to check that even unchanged snaps are accessible) 444 func Remodel(st *state.State, new *asserts.Model) (*state.Change, error) { 445 var seeded bool 446 err := st.Get("seeded", &seeded) 447 if err != nil && err != state.ErrNoState { 448 return nil, err 449 } 450 if !seeded { 451 return nil, fmt.Errorf("cannot remodel until fully seeded") 452 } 453 454 current, err := findModel(st) 455 if err != nil { 456 return nil, err 457 } 458 if current.Series() != new.Series() { 459 return nil, fmt.Errorf("cannot remodel to different series yet") 460 } 461 462 // TODO: we need dedicated assertion language to permit for 463 // model transitions before we allow cross vault 464 // transitions. 465 466 remodelKind := ClassifyRemodel(current, new) 467 468 // TODO: should we restrict remodel from one arch to another? 469 // There are valid use-cases here though, i.e. amd64 machine that 470 // remodels itself to/from i386 (if the HW can do both 32/64 bit) 471 if current.Architecture() != new.Architecture() { 472 return nil, fmt.Errorf("cannot remodel to different architectures yet") 473 } 474 475 // calculate snap differences between the two models 476 // FIXME: this needs work to switch the base to boot as well 477 if current.Base() != new.Base() { 478 return nil, fmt.Errorf("cannot remodel to different bases yet") 479 } 480 if current.Gadget() != new.Gadget() { 481 return nil, fmt.Errorf("cannot remodel to different gadgets yet") 482 } 483 484 // TODO: should we run a remodel only while no other change is 485 // running? do we add a task upfront that waits for that to be 486 // true? Do we do this only for the more complicated cases 487 // (anything more than adding required-snaps really)? 488 489 remodCtx, err := remodelCtx(st, current, new) 490 if err != nil { 491 return nil, err 492 } 493 494 var tss []*state.TaskSet 495 switch remodelKind { 496 case ReregRemodel: 497 // nothing else can be in-flight 498 for _, chg := range st.Changes() { 499 if !chg.IsReady() { 500 return nil, &snapstate.ChangeConflictError{Message: "cannot start complete remodel, other changes are in progress"} 501 } 502 } 503 504 requestSerial := st.NewTask("request-serial", i18n.G("Request new device serial")) 505 506 prepare := st.NewTask("prepare-remodeling", i18n.G("Prepare remodeling")) 507 prepare.WaitFor(requestSerial) 508 ts := state.NewTaskSet(requestSerial, prepare) 509 tss = []*state.TaskSet{ts} 510 case StoreSwitchRemodel: 511 sto := remodCtx.Store() 512 if sto == nil { 513 return nil, fmt.Errorf("internal error: a store switch remodeling should have built a store") 514 } 515 // ensure a new session accounting for the new brand store 516 st.Unlock() 517 _, err := sto.EnsureDeviceSession() 518 st.Lock() 519 if err != nil { 520 return nil, fmt.Errorf("cannot get a store session based on the new model assertion: %v", err) 521 } 522 fallthrough 523 case UpdateRemodel: 524 var err error 525 tss, err = remodelTasks(context.TODO(), st, current, new, remodCtx, "") 526 if err != nil { 527 return nil, err 528 } 529 } 530 531 // we potentially released the lock a couple of times here: 532 // make sure the current model is essentially the same as when 533 // we started 534 current1, err := findModel(st) 535 if err != nil { 536 return nil, err 537 } 538 if current.BrandID() != current1.BrandID() || current.Model() != current1.Model() || current.Revision() != current1.Revision() { 539 return nil, &snapstate.ChangeConflictError{Message: fmt.Sprintf("cannot start remodel, clashing with concurrent remodel to %v/%v (%v)", current1.BrandID(), current1.Model(), current1.Revision())} 540 } 541 // make sure another unfinished remodel wasn't already setup either 542 if Remodeling(st) { 543 return nil, &snapstate.ChangeConflictError{Message: "cannot start remodel, clashing with concurrent one"} 544 } 545 546 var msg string 547 if current.BrandID() == new.BrandID() && current.Model() == new.Model() { 548 msg = fmt.Sprintf(i18n.G("Refresh model assertion from revision %v to %v"), current.Revision(), new.Revision()) 549 } else { 550 msg = fmt.Sprintf(i18n.G("Remodel device to %v/%v (%v)"), new.BrandID(), new.Model(), new.Revision()) 551 } 552 553 chg := st.NewChange("remodel", msg) 554 remodCtx.Init(chg) 555 for _, ts := range tss { 556 chg.AddAll(ts) 557 } 558 559 return chg, nil 560 } 561 562 // Remodeling returns true whether there's a remodeling in progress 563 func Remodeling(st *state.State) bool { 564 for _, chg := range st.Changes() { 565 if !chg.IsReady() && chg.Kind() == "remodel" { 566 return true 567 } 568 } 569 return false 570 }