github.com/bugraaydogar/snapd@v0.0.0-20210315170335-8c70bb858939/overlord/snapstate/snapstate.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2016-2018 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 snapstate implements the manager and state aspects responsible for the installation and removal of snaps. 21 package snapstate 22 23 import ( 24 "context" 25 "encoding/json" 26 "errors" 27 "fmt" 28 "os" 29 "sort" 30 "strings" 31 "time" 32 33 "github.com/snapcore/snapd/asserts" 34 "github.com/snapcore/snapd/boot" 35 "github.com/snapcore/snapd/dirs" 36 "github.com/snapcore/snapd/features" 37 "github.com/snapcore/snapd/gadget" 38 "github.com/snapcore/snapd/i18n" 39 "github.com/snapcore/snapd/interfaces" 40 "github.com/snapcore/snapd/logger" 41 "github.com/snapcore/snapd/osutil" 42 "github.com/snapcore/snapd/overlord/auth" 43 "github.com/snapcore/snapd/overlord/configstate/config" 44 "github.com/snapcore/snapd/overlord/ifacestate/ifacerepo" 45 "github.com/snapcore/snapd/overlord/snapstate/backend" 46 "github.com/snapcore/snapd/overlord/state" 47 "github.com/snapcore/snapd/release" 48 "github.com/snapcore/snapd/snap" 49 "github.com/snapcore/snapd/snap/channel" 50 "github.com/snapcore/snapd/store" 51 "github.com/snapcore/snapd/strutil" 52 ) 53 54 // control flags for doInstall 55 const ( 56 skipConfigure = 1 << iota 57 ) 58 59 // control flags for "Configure()" 60 const ( 61 IgnoreHookError = 1 << iota 62 TrackHookError 63 UseConfigDefaults 64 ) 65 66 const ( 67 DownloadAndChecksDoneEdge = state.TaskSetEdge("download-and-checks-done") 68 BeginEdge = state.TaskSetEdge("begin") 69 BeforeHooksEdge = state.TaskSetEdge("before-hooks") 70 HooksEdge = state.TaskSetEdge("hooks") 71 ) 72 73 var ErrNothingToDo = errors.New("nothing to do") 74 75 var osutilCheckFreeSpace = osutil.CheckFreeSpace 76 77 // InsufficientSpaceError represents an error where there is not enough disk 78 // space to perform an operation. 79 type InsufficientSpaceError struct { 80 // Path is the filesystem path checked for available disk space 81 Path string 82 // Snaps affected by the failing operation 83 Snaps []string 84 // Kind of the change that failed 85 ChangeKind string 86 // Message is optional, otherwise one is composed from the other information 87 Message string 88 } 89 90 func (e *InsufficientSpaceError) Error() string { 91 if e.Message != "" { 92 return e.Message 93 } 94 if len(e.Snaps) > 0 { 95 snaps := strings.Join(e.Snaps, ", ") 96 return fmt.Sprintf("insufficient space in %q to perform %q change for the following snaps: %s", e.Path, e.ChangeKind, snaps) 97 } 98 return fmt.Sprintf("insufficient space in %q", e.Path) 99 } 100 101 // safetyMarginDiskSpace returns size plus a safety margin (5Mb) 102 func safetyMarginDiskSpace(size uint64) uint64 { 103 return size + 5*1024*1024 104 } 105 106 func isParallelInstallable(snapsup *SnapSetup) error { 107 if snapsup.InstanceKey == "" { 108 return nil 109 } 110 if snapsup.Type == snap.TypeApp { 111 return nil 112 } 113 return fmt.Errorf("cannot install snap of type %v as %q", snapsup.Type, snapsup.InstanceName()) 114 } 115 116 func optedIntoSnapdSnap(st *state.State) (bool, error) { 117 tr := config.NewTransaction(st) 118 experimentalAllowSnapd, err := features.Flag(tr, features.SnapdSnap) 119 if err != nil && !config.IsNoOption(err) { 120 return false, err 121 } 122 return experimentalAllowSnapd, nil 123 } 124 125 func doInstall(st *state.State, snapst *SnapState, snapsup *SnapSetup, flags int, fromChange string, inUseCheck func(snap.Type) (boot.InUseFunc, error)) (*state.TaskSet, error) { 126 // NB: we should strive not to need or propagate deviceCtx 127 // here, the resulting effects/changes were not pleasant at 128 // one point 129 tr := config.NewTransaction(st) 130 experimentalRefreshAppAwareness, err := features.Flag(tr, features.RefreshAppAwareness) 131 if err != nil && !config.IsNoOption(err) { 132 return nil, err 133 } 134 135 if snapsup.InstanceName() == "system" { 136 return nil, fmt.Errorf("cannot install reserved snap name 'system'") 137 } 138 if snapst.IsInstalled() && !snapst.Active { 139 return nil, fmt.Errorf("cannot update disabled snap %q", snapsup.InstanceName()) 140 } 141 if snapst.IsInstalled() && !snapsup.Flags.Revert { 142 if inUseCheck == nil { 143 return nil, fmt.Errorf("internal error: doInstall: inUseCheck not provided for refresh") 144 } 145 } 146 147 if snapsup.Flags.Classic { 148 if !release.OnClassic { 149 return nil, fmt.Errorf("classic confinement is only supported on classic systems") 150 } else if !dirs.SupportsClassicConfinement() { 151 return nil, fmt.Errorf(i18n.G("classic confinement requires snaps under /snap or symlink from /snap to %s"), dirs.SnapMountDir) 152 } 153 } 154 if !snapst.IsInstalled() { // install? 155 // check that the snap command namespace doesn't conflict with an enabled alias 156 if err := checkSnapAliasConflict(st, snapsup.InstanceName()); err != nil { 157 return nil, err 158 } 159 } 160 161 if err := isParallelInstallable(snapsup); err != nil { 162 return nil, err 163 } 164 165 if err := checkChangeConflictIgnoringOneChange(st, snapsup.InstanceName(), snapst, fromChange); err != nil { 166 return nil, err 167 } 168 169 if snapst.IsInstalled() { 170 // consider also the current revision to set plugs-only hint 171 info, err := snapst.CurrentInfo() 172 if err != nil { 173 return nil, err 174 } 175 snapsup.PlugsOnly = snapsup.PlugsOnly && (len(info.Slots) == 0) 176 177 if experimentalRefreshAppAwareness && !snapsup.Flags.IgnoreRunning { 178 // Note that because we are modifying the snap state inside 179 // softCheckNothingRunningForRefresh, this block must be located 180 // after the conflict check done above. 181 if err := softCheckNothingRunningForRefresh(st, snapst, info); err != nil { 182 return nil, err 183 } 184 } 185 } 186 187 ts := state.NewTaskSet() 188 189 targetRevision := snapsup.Revision() 190 revisionStr := "" 191 if snapsup.SideInfo != nil { 192 revisionStr = fmt.Sprintf(" (%s)", targetRevision) 193 } 194 195 // check if we already have the revision locally (alters tasks) 196 revisionIsLocal := snapst.LastIndex(targetRevision) >= 0 197 198 prereq := st.NewTask("prerequisites", fmt.Sprintf(i18n.G("Ensure prerequisites for %q are available"), snapsup.InstanceName())) 199 prereq.Set("snap-setup", snapsup) 200 201 var prepare, prev *state.Task 202 fromStore := false 203 // if we have a local revision here we go back to that 204 if snapsup.SnapPath != "" || revisionIsLocal { 205 prepare = st.NewTask("prepare-snap", fmt.Sprintf(i18n.G("Prepare snap %q%s"), snapsup.SnapPath, revisionStr)) 206 } else { 207 fromStore = true 208 prepare = st.NewTask("download-snap", fmt.Sprintf(i18n.G("Download snap %q%s from channel %q"), snapsup.InstanceName(), revisionStr, snapsup.Channel)) 209 } 210 prepare.Set("snap-setup", snapsup) 211 prepare.WaitFor(prereq) 212 213 tasks := []*state.Task{prereq, prepare} 214 addTask := func(t *state.Task) { 215 t.Set("snap-setup-task", prepare.ID()) 216 t.WaitFor(prev) 217 tasks = append(tasks, t) 218 } 219 prev = prepare 220 221 var checkAsserts *state.Task 222 if fromStore { 223 // fetch and check assertions 224 checkAsserts = st.NewTask("validate-snap", fmt.Sprintf(i18n.G("Fetch and check assertions for snap %q%s"), snapsup.InstanceName(), revisionStr)) 225 addTask(checkAsserts) 226 prev = checkAsserts 227 } 228 229 // mount 230 if !revisionIsLocal { 231 mount := st.NewTask("mount-snap", fmt.Sprintf(i18n.G("Mount snap %q%s"), snapsup.InstanceName(), revisionStr)) 232 addTask(mount) 233 prev = mount 234 } 235 236 // run refresh hooks when updating existing snap, otherwise run install hook further down. 237 runRefreshHooks := (snapst.IsInstalled() && !snapsup.Flags.Revert) 238 if runRefreshHooks { 239 preRefreshHook := SetupPreRefreshHook(st, snapsup.InstanceName()) 240 addTask(preRefreshHook) 241 prev = preRefreshHook 242 } 243 244 if snapst.IsInstalled() { 245 // unlink-current-snap (will stop services for copy-data) 246 stop := st.NewTask("stop-snap-services", fmt.Sprintf(i18n.G("Stop snap %q services"), snapsup.InstanceName())) 247 stop.Set("stop-reason", snap.StopReasonRefresh) 248 addTask(stop) 249 prev = stop 250 251 removeAliases := st.NewTask("remove-aliases", fmt.Sprintf(i18n.G("Remove aliases for snap %q"), snapsup.InstanceName())) 252 addTask(removeAliases) 253 prev = removeAliases 254 255 unlink := st.NewTask("unlink-current-snap", fmt.Sprintf(i18n.G("Make current revision for snap %q unavailable"), snapsup.InstanceName())) 256 addTask(unlink) 257 prev = unlink 258 } 259 260 if !release.OnClassic && (snapsup.Type == snap.TypeGadget || snapsup.Type == snap.TypeKernel) { 261 // XXX: gadget update currently for core systems only 262 gadgetUpdate := st.NewTask("update-gadget-assets", fmt.Sprintf(i18n.G("Update assets from gadget %q%s"), snapsup.InstanceName(), revisionStr)) 263 addTask(gadgetUpdate) 264 prev = gadgetUpdate 265 } 266 267 // copy-data (needs stopped services by unlink) 268 if !snapsup.Flags.Revert { 269 copyData := st.NewTask("copy-snap-data", fmt.Sprintf(i18n.G("Copy snap %q data"), snapsup.InstanceName())) 270 addTask(copyData) 271 prev = copyData 272 } 273 274 // security 275 setupSecurity := st.NewTask("setup-profiles", fmt.Sprintf(i18n.G("Setup snap %q%s security profiles"), snapsup.InstanceName(), revisionStr)) 276 addTask(setupSecurity) 277 prev = setupSecurity 278 279 // finalize (wrappers+current symlink) 280 linkSnap := st.NewTask("link-snap", fmt.Sprintf(i18n.G("Make snap %q%s available to the system"), snapsup.InstanceName(), revisionStr)) 281 addTask(linkSnap) 282 prev = linkSnap 283 284 // auto-connections 285 autoConnect := st.NewTask("auto-connect", fmt.Sprintf(i18n.G("Automatically connect eligible plugs and slots of snap %q"), snapsup.InstanceName())) 286 addTask(autoConnect) 287 prev = autoConnect 288 289 // setup aliases 290 setAutoAliases := st.NewTask("set-auto-aliases", fmt.Sprintf(i18n.G("Set automatic aliases for snap %q"), snapsup.InstanceName())) 291 addTask(setAutoAliases) 292 prev = setAutoAliases 293 294 setupAliases := st.NewTask("setup-aliases", fmt.Sprintf(i18n.G("Setup snap %q aliases"), snapsup.InstanceName())) 295 addTask(setupAliases) 296 prev = setupAliases 297 298 if !release.OnClassic && snapsup.Type == snap.TypeSnapd { 299 // only run for core devices and the snapd snap, run late enough 300 // so that the task is executed by the new snapd 301 bootConfigUpdate := st.NewTask("update-managed-boot-config", fmt.Sprintf(i18n.G("Update managed boot config assets from %q%s"), snapsup.InstanceName(), revisionStr)) 302 addTask(bootConfigUpdate) 303 prev = bootConfigUpdate 304 } 305 306 if runRefreshHooks { 307 postRefreshHook := SetupPostRefreshHook(st, snapsup.InstanceName()) 308 addTask(postRefreshHook) 309 prev = postRefreshHook 310 } 311 312 var installHook *state.Task 313 // only run install hook if installing the snap for the first time 314 if !snapst.IsInstalled() { 315 installHook = SetupInstallHook(st, snapsup.InstanceName()) 316 addTask(installHook) 317 prev = installHook 318 } 319 320 // run new services 321 startSnapServices := st.NewTask("start-snap-services", fmt.Sprintf(i18n.G("Start snap %q%s services"), snapsup.InstanceName(), revisionStr)) 322 addTask(startSnapServices) 323 prev = startSnapServices 324 325 // Do not do that if we are reverting to a local revision 326 if snapst.IsInstalled() && !snapsup.Flags.Revert { 327 var retain int 328 if err := config.NewTransaction(st).Get("core", "refresh.retain", &retain); err != nil { 329 // on classic we only keep 2 copies by default 330 if release.OnClassic { 331 retain = 2 332 } else { 333 retain = 3 334 } 335 } 336 retain-- // we're adding one 337 338 seq := snapst.Sequence 339 currentIndex := snapst.LastIndex(snapst.Current) 340 341 // discard everything after "current" (we may have reverted to 342 // a previous versions earlier) 343 for i := currentIndex + 1; i < len(seq); i++ { 344 si := seq[i] 345 if si.Revision == targetRevision { 346 // but don't discard this one; its' the thing we're switching to! 347 continue 348 } 349 ts := removeInactiveRevision(st, snapsup.InstanceName(), si.SnapID, si.Revision) 350 ts.WaitFor(prev) 351 tasks = append(tasks, ts.Tasks()...) 352 prev = tasks[len(tasks)-1] 353 } 354 355 // make sure we're not scheduling the removal of the target 356 // revision in the case where the target revision is already in 357 // the sequence. 358 for i := 0; i < currentIndex; i++ { 359 si := seq[i] 360 if si.Revision == targetRevision { 361 // we do *not* want to removeInactiveRevision of this one 362 copy(seq[i:], seq[i+1:]) 363 seq = seq[:len(seq)-1] 364 currentIndex-- 365 } 366 } 367 368 // normal garbage collect 369 var inUse boot.InUseFunc 370 for i := 0; i <= currentIndex-retain; i++ { 371 if inUse == nil { 372 var err error 373 inUse, err = inUseCheck(snapsup.Type) 374 if err != nil { 375 return nil, err 376 } 377 } 378 379 si := seq[i] 380 if inUse(snapsup.InstanceName(), si.Revision) { 381 continue 382 } 383 ts := removeInactiveRevision(st, snapsup.InstanceName(), si.SnapID, si.Revision) 384 ts.WaitFor(prev) 385 tasks = append(tasks, ts.Tasks()...) 386 prev = tasks[len(tasks)-1] 387 } 388 389 addTask(st.NewTask("cleanup", fmt.Sprintf("Clean up %q%s install", snapsup.InstanceName(), revisionStr))) 390 } 391 392 installSet := state.NewTaskSet(tasks...) 393 installSet.WaitAll(ts) 394 installSet.MarkEdge(prereq, BeginEdge) 395 installSet.MarkEdge(setupAliases, BeforeHooksEdge) 396 if installHook != nil { 397 installSet.MarkEdge(installHook, HooksEdge) 398 } 399 ts.AddAllWithEdges(installSet) 400 if checkAsserts != nil { 401 ts.MarkEdge(checkAsserts, DownloadAndChecksDoneEdge) 402 } 403 404 if flags&skipConfigure != 0 { 405 return installSet, nil 406 } 407 408 // we do not support configuration for bases or the "snapd" snap yet 409 if snapsup.Type != snap.TypeBase && snapsup.Type != snap.TypeSnapd { 410 confFlags := 0 411 notCore := snapsup.InstanceName() != "core" 412 hasSnapID := snapsup.SideInfo != nil && snapsup.SideInfo.SnapID != "" 413 if !snapst.IsInstalled() && hasSnapID && notCore { 414 // installation, run configure using the gadget defaults 415 // if available, system config defaults (attached to 416 // "core") are consumed only during seeding, via an 417 // explicit configure step separate from installing 418 confFlags |= UseConfigDefaults 419 } 420 configSet := ConfigureSnap(st, snapsup.InstanceName(), confFlags) 421 configSet.WaitAll(ts) 422 ts.AddAll(configSet) 423 } 424 425 healthCheck := CheckHealthHook(st, snapsup.InstanceName(), snapsup.Revision()) 426 healthCheck.WaitAll(ts) 427 ts.AddTask(healthCheck) 428 429 return ts, nil 430 } 431 432 // ConfigureSnap returns a set of tasks to configure snapName as done during installation/refresh. 433 func ConfigureSnap(st *state.State, snapName string, confFlags int) *state.TaskSet { 434 // This is slightly ugly, ideally we would check the type instead 435 // of hardcoding the name here. Unfortunately we do not have the 436 // type until we actually run the change. 437 if snapName == defaultCoreSnapName { 438 confFlags |= IgnoreHookError 439 confFlags |= TrackHookError 440 } 441 return Configure(st, snapName, nil, confFlags) 442 } 443 444 var Configure = func(st *state.State, snapName string, patch map[string]interface{}, flags int) *state.TaskSet { 445 panic("internal error: snapstate.Configure is unset") 446 } 447 448 var SetupInstallHook = func(st *state.State, snapName string) *state.Task { 449 panic("internal error: snapstate.SetupInstallHook is unset") 450 } 451 452 var SetupPreRefreshHook = func(st *state.State, snapName string) *state.Task { 453 panic("internal error: snapstate.SetupPreRefreshHook is unset") 454 } 455 456 var SetupPostRefreshHook = func(st *state.State, snapName string) *state.Task { 457 panic("internal error: snapstate.SetupPostRefreshHook is unset") 458 } 459 460 var SetupRemoveHook = func(st *state.State, snapName string) *state.Task { 461 panic("internal error: snapstate.SetupRemoveHook is unset") 462 } 463 464 var CheckHealthHook = func(st *state.State, snapName string, rev snap.Revision) *state.Task { 465 panic("internal error: snapstate.CheckHealthHook is unset") 466 } 467 468 var generateSnapdWrappers = backend.GenerateSnapdWrappers 469 470 // FinishRestart will return a Retry error if there is a pending restart 471 // and a real error if anything went wrong (like a rollback across 472 // restarts). 473 // For snapd snap updates this will also rerun wrappers generation to fully 474 // catch up with any change. 475 func FinishRestart(task *state.Task, snapsup *SnapSetup) (err error) { 476 if ok, _ := task.State().Restarting(); ok { 477 // don't continue until we are in the restarted snapd 478 task.Logf("Waiting for automatic snapd restart...") 479 return &state.Retry{} 480 } 481 482 if snapsup.Type == snap.TypeSnapd { 483 if os.Getenv("SNAPD_REVERT_TO_REV") != "" { 484 return fmt.Errorf("there was a snapd rollback across the restart") 485 } 486 487 // if we have restarted and snapd was refreshed, then we need to generate 488 // snapd wrappers again with current snapd, as the logic of generating 489 // wrappers may have changed between previous and new snapd code. 490 if !release.OnClassic { 491 snapdInfo, err := snap.ReadCurrentInfo(snapsup.SnapName()) 492 if err != nil { 493 return fmt.Errorf("cannot get current snapd snap info: %v", err) 494 } 495 // TODO: if future changes to wrappers need one more snapd restart, 496 // then it should be handled here as well. 497 if err := generateSnapdWrappers(snapdInfo); err != nil { 498 return err 499 } 500 } 501 } 502 503 // consider kernel and base 504 505 deviceCtx, err := DeviceCtx(task.State(), task, nil) 506 if err != nil { 507 return err 508 } 509 510 // Check if there was a rollback. A reboot can be triggered by: 511 // - core (old core16 world, system-reboot) 512 // - bootable base snap (new core18 world, system-reboot) 513 // - kernel 514 // 515 // On classic and in ephemeral run modes (like install, recover) 516 // there can never be a rollback so we can skip the check there. 517 // 518 // TODO: Detect "snapd" snap daemon-restarts here that 519 // fallback into the old version (once we have 520 // better snapd rollback support in core18). 521 if deviceCtx.RunMode() && !release.OnClassic { 522 // get the name of the name relevant for booting 523 // based on the given type 524 model := deviceCtx.Model() 525 var bootName string 526 switch snapsup.Type { 527 case snap.TypeKernel: 528 bootName = model.Kernel() 529 case snap.TypeOS, snap.TypeBase: 530 bootName = "core" 531 if model.Base() != "" { 532 bootName = model.Base() 533 } 534 default: 535 return nil 536 } 537 // if it is not a snap related to our booting we are not 538 // interested 539 if snapsup.InstanceName() != bootName { 540 return nil 541 } 542 543 // compare what we think is "current" for snapd with what 544 // actually booted. The bootloader may revert on a failed 545 // boot from a bad os/base/kernel to a good one and in this 546 // case we need to catch this and error accordingly 547 current, err := boot.GetCurrentBoot(snapsup.Type, deviceCtx) 548 if err == boot.ErrBootNameAndRevisionNotReady { 549 return &state.Retry{After: 5 * time.Second} 550 } 551 if err != nil { 552 return err 553 } 554 if snapsup.InstanceName() != current.SnapName() || snapsup.SideInfo.Revision != current.SnapRevision() { 555 // TODO: make sure this revision gets ignored for 556 // automatic refreshes 557 return fmt.Errorf("cannot finish %s installation, there was a rollback across reboot", snapsup.InstanceName()) 558 } 559 } 560 561 return nil 562 } 563 564 func contentAttr(attrer interfaces.Attrer) string { 565 var s string 566 err := attrer.Attr("content", &s) 567 if err != nil { 568 return "" 569 } 570 return s 571 } 572 573 func contentIfaceAvailable(st *state.State) map[string]bool { 574 repo := ifacerepo.Get(st) 575 contentSlots := repo.AllSlots("content") 576 avail := make(map[string]bool, len(contentSlots)) 577 for _, slot := range contentSlots { 578 contentTag := contentAttr(slot) 579 if contentTag == "" { 580 continue 581 } 582 avail[contentTag] = true 583 } 584 return avail 585 } 586 587 // defaultContentPlugProviders takes a snap.Info and returns what 588 // default providers there are. 589 func defaultContentPlugProviders(st *state.State, info *snap.Info) []string { 590 needed := snap.NeededDefaultProviders(info) 591 if len(needed) == 0 { 592 return nil 593 } 594 avail := contentIfaceAvailable(st) 595 out := []string{} 596 for snapInstance, contentTags := range needed { 597 for _, contentTag := range contentTags { 598 if !avail[contentTag] { 599 out = append(out, snapInstance) 600 break 601 } 602 } 603 } 604 return out 605 } 606 607 // validateFeatureFlags validates the given snap only uses experimental 608 // features that are enabled by the user. 609 func validateFeatureFlags(st *state.State, info *snap.Info) error { 610 tr := config.NewTransaction(st) 611 612 if len(info.Layout) > 0 { 613 flag, err := features.Flag(tr, features.Layouts) 614 if err != nil { 615 return err 616 } 617 if !flag { 618 return fmt.Errorf("experimental feature disabled - test it by setting 'experimental.layouts' to true") 619 } 620 } 621 622 if info.InstanceKey != "" { 623 flag, err := features.Flag(tr, features.ParallelInstances) 624 if err != nil { 625 return err 626 } 627 if !flag { 628 return fmt.Errorf("experimental feature disabled - test it by setting 'experimental.parallel-instances' to true") 629 } 630 } 631 632 var hasUserService, usesDbusActivation bool 633 for _, app := range info.Apps { 634 if app.IsService() && app.DaemonScope == snap.UserDaemon { 635 hasUserService = true 636 } 637 if len(app.ActivatesOn) != 0 { 638 usesDbusActivation = true 639 } 640 } 641 642 if hasUserService { 643 flag, err := features.Flag(tr, features.UserDaemons) 644 if err != nil { 645 return err 646 } 647 if !flag { 648 return fmt.Errorf("experimental feature disabled - test it by setting 'experimental.user-daemons' to true") 649 } 650 if !release.SystemctlSupportsUserUnits() { 651 return fmt.Errorf("user session daemons are not supported on this release") 652 } 653 } 654 655 if usesDbusActivation { 656 flag, err := features.Flag(tr, features.DbusActivation) 657 if err != nil { 658 return err 659 } 660 if !flag { 661 return fmt.Errorf("experimental feature disabled - test it by setting 'experimental.dbus-activation' to true") 662 } 663 } 664 665 return nil 666 } 667 668 func ensureInstallPreconditions(st *state.State, info *snap.Info, flags Flags, snapst *SnapState, deviceCtx DeviceContext) (Flags, error) { 669 // if snap is allowed to be devmode via the dangerous model and it's 670 // confinement is indeed devmode, promote the flags.DevMode to true 671 if flags.ApplySnapDevMode && info.NeedsDevMode() { 672 // TODO: what about jail-mode? will we also allow putting devmode 673 // snaps (i.e. snaps with snap.yaml with confinement: devmode) into 674 // strict confinement via the model assertion? 675 flags.DevMode = true 676 } 677 678 if flags.Classic && !info.NeedsClassic() { 679 // snap does not require classic confinement, silently drop the flag 680 flags.Classic = false 681 } 682 683 if err := validateInfoAndFlags(info, snapst, flags); err != nil { 684 return flags, err 685 } 686 if err := validateFeatureFlags(st, info); err != nil { 687 return flags, err 688 } 689 if err := checkDBusServiceConflicts(st, info); err != nil { 690 return flags, err 691 } 692 return flags, nil 693 } 694 695 // InstallPath returns a set of tasks for installing a snap from a file path 696 // and the snap.Info for the given snap. 697 // 698 // Note that the state must be locked by the caller. 699 // The provided SideInfo can contain just a name which results in a 700 // local revision and sideloading, or full metadata in which case it 701 // the snap will appear as installed from the store. 702 func InstallPath(st *state.State, si *snap.SideInfo, path, instanceName, channel string, flags Flags) (*state.TaskSet, *snap.Info, error) { 703 if si.RealName == "" { 704 return nil, nil, fmt.Errorf("internal error: snap name to install %q not provided", path) 705 } 706 707 if instanceName == "" { 708 instanceName = si.RealName 709 } 710 711 deviceCtx, err := DeviceCtxFromState(st, nil) 712 if err != nil { 713 return nil, nil, err 714 } 715 716 var snapst SnapState 717 err = Get(st, instanceName, &snapst) 718 if err != nil && err != state.ErrNoState { 719 return nil, nil, err 720 } 721 722 if si.SnapID != "" { 723 if si.Revision.Unset() { 724 return nil, nil, fmt.Errorf("internal error: snap id set to install %q but revision is unset", path) 725 } 726 } 727 728 channel, err = resolveChannel(st, instanceName, snapst.TrackingChannel, channel, deviceCtx) 729 if err != nil { 730 return nil, nil, err 731 } 732 733 var instFlags int 734 if flags.SkipConfigure { 735 // extract it as a doInstall flag, this is not passed 736 // into SnapSetup 737 instFlags |= skipConfigure 738 } 739 740 // It is ok do open the snap file here because we either 741 // have side info or the user passed --dangerous 742 info, container, err := backend.OpenSnapFile(path, si) 743 if err != nil { 744 return nil, nil, err 745 } 746 747 if err := validateContainer(container, info, logger.Noticef); err != nil { 748 return nil, nil, err 749 } 750 if err := snap.ValidateInstanceName(instanceName); err != nil { 751 return nil, nil, fmt.Errorf("invalid instance name: %v", err) 752 } 753 754 snapName, instanceKey := snap.SplitInstanceName(instanceName) 755 if info.SnapName() != snapName { 756 return nil, nil, fmt.Errorf("cannot install snap %q, the name does not match the metadata %q", instanceName, info.SnapName()) 757 } 758 info.InstanceKey = instanceKey 759 760 flags, err = ensureInstallPreconditions(st, info, flags, &snapst, deviceCtx) 761 if err != nil { 762 return nil, nil, err 763 } 764 // this might be a refresh; check the epoch before proceeding 765 if err := earlyEpochCheck(info, &snapst); err != nil { 766 return nil, nil, err 767 } 768 769 snapsup := &SnapSetup{ 770 Base: info.Base, 771 Prereq: defaultContentPlugProviders(st, info), 772 SideInfo: si, 773 SnapPath: path, 774 Channel: channel, 775 Flags: flags.ForSnapSetup(), 776 Type: info.Type(), 777 PlugsOnly: len(info.Slots) == 0, 778 InstanceKey: info.InstanceKey, 779 } 780 781 ts, err := doInstall(st, &snapst, snapsup, instFlags, "", inUseFor(deviceCtx)) 782 return ts, info, err 783 } 784 785 // TryPath returns a set of tasks for trying a snap from a file path. 786 // Note that the state must be locked by the caller. 787 func TryPath(st *state.State, name, path string, flags Flags) (*state.TaskSet, error) { 788 flags.TryMode = true 789 790 ts, _, err := InstallPath(st, &snap.SideInfo{RealName: name}, path, "", "", flags) 791 return ts, err 792 } 793 794 // Install returns a set of tasks for installing a snap. 795 // Note that the state must be locked by the caller. 796 // 797 // The returned TaskSet will contain a DownloadAndChecksDoneEdge. 798 func Install(ctx context.Context, st *state.State, name string, opts *RevisionOptions, userID int, flags Flags) (*state.TaskSet, error) { 799 return InstallWithDeviceContext(ctx, st, name, opts, userID, flags, nil, "") 800 } 801 802 // InstallWithDeviceContext returns a set of tasks for installing a snap. 803 // It will query for the snap with the given deviceCtx. 804 // Note that the state must be locked by the caller. 805 // 806 // The returned TaskSet will contain a DownloadAndChecksDoneEdge. 807 func InstallWithDeviceContext(ctx context.Context, st *state.State, name string, opts *RevisionOptions, userID int, flags Flags, deviceCtx DeviceContext, fromChange string) (*state.TaskSet, error) { 808 if opts == nil { 809 opts = &RevisionOptions{} 810 } 811 if opts.CohortKey != "" && !opts.Revision.Unset() { 812 return nil, errors.New("cannot specify revision and cohort") 813 } 814 815 if opts.Channel == "" { 816 opts.Channel = "stable" 817 } 818 819 var snapst SnapState 820 err := Get(st, name, &snapst) 821 if err != nil && err != state.ErrNoState { 822 return nil, err 823 } 824 if snapst.IsInstalled() { 825 return nil, &snap.AlreadyInstalledError{Snap: name} 826 } 827 // need to have a model set before trying to talk the store 828 deviceCtx, err = DevicePastSeeding(st, deviceCtx) 829 if err != nil { 830 return nil, err 831 } 832 833 if err := snap.ValidateInstanceName(name); err != nil { 834 return nil, fmt.Errorf("invalid instance name: %v", err) 835 } 836 837 sar, err := installInfo(ctx, st, name, opts, userID, deviceCtx) 838 if err != nil { 839 return nil, err 840 } 841 info := sar.Info 842 843 if flags.RequireTypeBase && info.Type() != snap.TypeBase && info.Type() != snap.TypeOS { 844 return nil, fmt.Errorf("unexpected snap type %q, instead of 'base'", info.Type()) 845 } 846 847 flags, err = ensureInstallPreconditions(st, info, flags, &snapst, deviceCtx) 848 if err != nil { 849 return nil, err 850 } 851 852 tr := config.NewTransaction(st) 853 checkDiskSpaceInstall, err := features.Flag(tr, features.CheckDiskSpaceInstall) 854 if err != nil && !config.IsNoOption(err) { 855 return nil, err 856 } 857 if checkDiskSpaceInstall { 858 // check if there is enough disk space for requested snap and its 859 // prerequisites. 860 totalSize, err := installSize(st, []*snap.Info{info}, userID) 861 if err != nil { 862 return nil, err 863 } 864 requiredSpace := safetyMarginDiskSpace(totalSize) 865 path := dirs.SnapdStateDir(dirs.GlobalRootDir) 866 if err := osutilCheckFreeSpace(path, requiredSpace); err != nil { 867 if _, ok := err.(*osutil.NotEnoughDiskSpaceError); ok { 868 return nil, &InsufficientSpaceError{ 869 Path: path, 870 Snaps: []string{info.InstanceName()}, 871 ChangeKind: "install", 872 } 873 } 874 return nil, err 875 } 876 } 877 878 snapsup := &SnapSetup{ 879 Channel: opts.Channel, 880 Base: info.Base, 881 Prereq: defaultContentPlugProviders(st, info), 882 UserID: userID, 883 Flags: flags.ForSnapSetup(), 884 DownloadInfo: &info.DownloadInfo, 885 SideInfo: &info.SideInfo, 886 Type: info.Type(), 887 PlugsOnly: len(info.Slots) == 0, 888 InstanceKey: info.InstanceKey, 889 auxStoreInfo: auxStoreInfo{ 890 Media: info.Media, 891 Website: info.Website, 892 }, 893 CohortKey: opts.CohortKey, 894 } 895 896 if sar.RedirectChannel != "" { 897 snapsup.Channel = sar.RedirectChannel 898 } 899 900 return doInstall(st, &snapst, snapsup, 0, fromChange, nil) 901 } 902 903 // InstallMany installs everything from the given list of names. 904 // Note that the state must be locked by the caller. 905 func InstallMany(st *state.State, names []string, userID int) ([]string, []*state.TaskSet, error) { 906 // need to have a model set before trying to talk the store 907 deviceCtx, err := DevicePastSeeding(st, nil) 908 if err != nil { 909 return nil, nil, err 910 } 911 912 toInstall := make([]string, 0, len(names)) 913 for _, name := range names { 914 var snapst SnapState 915 err := Get(st, name, &snapst) 916 if err != nil && err != state.ErrNoState { 917 return nil, nil, err 918 } 919 if snapst.IsInstalled() { 920 continue 921 } 922 923 if err := snap.ValidateInstanceName(name); err != nil { 924 return nil, nil, fmt.Errorf("invalid instance name: %v", err) 925 } 926 927 toInstall = append(toInstall, name) 928 } 929 930 user, err := userFromUserID(st, userID) 931 if err != nil { 932 return nil, nil, err 933 } 934 935 installs, err := installCandidates(st, toInstall, "stable", user) 936 if err != nil { 937 return nil, nil, err 938 } 939 940 tr := config.NewTransaction(st) 941 checkDiskSpaceInstall, err := features.Flag(tr, features.CheckDiskSpaceInstall) 942 if err != nil && !config.IsNoOption(err) { 943 return nil, nil, err 944 } 945 if checkDiskSpaceInstall { 946 // check if there is enough disk space for requested snaps and their 947 // prerequisites. 948 snapInfos := make([]*snap.Info, len(installs)) 949 for i, sar := range installs { 950 snapInfos[i] = sar.Info 951 } 952 totalSize, err := installSize(st, snapInfos, userID) 953 if err != nil { 954 return nil, nil, err 955 } 956 957 requiredSpace := safetyMarginDiskSpace(totalSize) 958 path := dirs.SnapdStateDir(dirs.GlobalRootDir) 959 if err := osutilCheckFreeSpace(path, requiredSpace); err != nil { 960 if _, ok := err.(*osutil.NotEnoughDiskSpaceError); ok { 961 return nil, nil, &InsufficientSpaceError{ 962 Path: path, 963 Snaps: toInstall, 964 ChangeKind: "install", 965 } 966 } 967 return nil, nil, err 968 } 969 } 970 971 tasksets := make([]*state.TaskSet, 0, len(installs)) 972 for _, sar := range installs { 973 info := sar.Info 974 var snapst SnapState 975 var flags Flags 976 977 flags, err := ensureInstallPreconditions(st, info, flags, &snapst, deviceCtx) 978 if err != nil { 979 return nil, nil, err 980 } 981 982 channel := "stable" 983 if sar.RedirectChannel != "" { 984 channel = sar.RedirectChannel 985 } 986 987 snapsup := &SnapSetup{ 988 Channel: channel, 989 Base: info.Base, 990 Prereq: defaultContentPlugProviders(st, info), 991 UserID: userID, 992 Flags: flags.ForSnapSetup(), 993 DownloadInfo: &info.DownloadInfo, 994 SideInfo: &info.SideInfo, 995 Type: info.Type(), 996 PlugsOnly: len(info.Slots) == 0, 997 InstanceKey: info.InstanceKey, 998 } 999 1000 ts, err := doInstall(st, &snapst, snapsup, 0, "", inUseFor(deviceCtx)) 1001 if err != nil { 1002 return nil, nil, err 1003 } 1004 ts.JoinLane(st.NewLane()) 1005 tasksets = append(tasksets, ts) 1006 } 1007 1008 return toInstall, tasksets, nil 1009 } 1010 1011 // RefreshCandidates gets a list of candidates for update 1012 // Note that the state must be locked by the caller. 1013 func RefreshCandidates(st *state.State, user *auth.UserState) ([]*snap.Info, error) { 1014 updates, _, _, err := refreshCandidates(context.TODO(), st, nil, user, nil) 1015 return updates, err 1016 } 1017 1018 // ValidateRefreshes allows to hook validation into the handling of refresh candidates. 1019 var ValidateRefreshes func(st *state.State, refreshes []*snap.Info, ignoreValidation map[string]bool, userID int, deviceCtx DeviceContext) (validated []*snap.Info, err error) 1020 1021 // UpdateMany updates everything from the given list of names that the 1022 // store says is updateable. If the list is empty, update everything. 1023 // Note that the state must be locked by the caller. 1024 func UpdateMany(ctx context.Context, st *state.State, names []string, userID int, flags *Flags) ([]string, []*state.TaskSet, error) { 1025 return updateManyFiltered(ctx, st, names, userID, nil, flags, "") 1026 } 1027 1028 // updateFilter is the type of function that can be passed to 1029 // updateManyFromChange so it filters the updates. 1030 // 1031 // If the filter returns true, the update for that snap proceeds. If 1032 // it returns false, the snap is removed from the list of updates to 1033 // consider. 1034 type updateFilter func(*snap.Info, *SnapState) bool 1035 1036 func updateManyFiltered(ctx context.Context, st *state.State, names []string, userID int, filter updateFilter, flags *Flags, fromChange string) ([]string, []*state.TaskSet, error) { 1037 if flags == nil { 1038 flags = &Flags{} 1039 } 1040 user, err := userFromUserID(st, userID) 1041 if err != nil { 1042 return nil, nil, err 1043 } 1044 1045 // need to have a model set before trying to talk the store 1046 deviceCtx, err := DevicePastSeeding(st, nil) 1047 if err != nil { 1048 return nil, nil, err 1049 } 1050 1051 refreshOpts := &store.RefreshOptions{IsAutoRefresh: flags.IsAutoRefresh} 1052 updates, stateByInstanceName, ignoreValidation, err := refreshCandidates(ctx, st, names, user, refreshOpts) 1053 if err != nil { 1054 return nil, nil, err 1055 } 1056 1057 if filter != nil { 1058 actual := updates[:0] 1059 for _, update := range updates { 1060 if filter(update, stateByInstanceName[update.InstanceName()]) { 1061 actual = append(actual, update) 1062 } 1063 } 1064 updates = actual 1065 } 1066 1067 if ValidateRefreshes != nil && len(updates) != 0 { 1068 updates, err = ValidateRefreshes(st, updates, ignoreValidation, userID, deviceCtx) 1069 if err != nil { 1070 // not doing "refresh all" report the error 1071 if len(names) != 0 { 1072 return nil, nil, err 1073 } 1074 // doing "refresh all", log the problems 1075 logger.Noticef("cannot refresh some snaps: %v", err) 1076 } 1077 } 1078 1079 params := func(update *snap.Info) (*RevisionOptions, Flags, *SnapState) { 1080 snapst := stateByInstanceName[update.InstanceName()] 1081 // setting options to what's in state as multi-refresh doesn't let you change these 1082 opts := &RevisionOptions{ 1083 Channel: snapst.TrackingChannel, 1084 CohortKey: snapst.CohortKey, 1085 } 1086 return opts, snapst.Flags, snapst 1087 1088 } 1089 1090 tr := config.NewTransaction(st) 1091 checkDiskSpaceRefresh, err := features.Flag(tr, features.CheckDiskSpaceRefresh) 1092 if err != nil && !config.IsNoOption(err) { 1093 return nil, nil, err 1094 } 1095 if checkDiskSpaceRefresh { 1096 // check if there is enough disk space for requested snap and its 1097 // prerequisites. 1098 totalSize, err := installSize(st, updates, userID) 1099 if err != nil { 1100 return nil, nil, err 1101 } 1102 requiredSpace := safetyMarginDiskSpace(totalSize) 1103 path := dirs.SnapdStateDir(dirs.GlobalRootDir) 1104 if err := osutilCheckFreeSpace(path, requiredSpace); err != nil { 1105 snaps := make([]string, len(updates)) 1106 for i, up := range updates { 1107 snaps[i] = up.InstanceName() 1108 } 1109 if _, ok := err.(*osutil.NotEnoughDiskSpaceError); ok { 1110 return nil, nil, &InsufficientSpaceError{ 1111 Path: path, 1112 Snaps: snaps, 1113 ChangeKind: "refresh", 1114 } 1115 } 1116 return nil, nil, err 1117 } 1118 } 1119 1120 updated, tasksets, err := doUpdate(ctx, st, names, updates, params, userID, flags, deviceCtx, fromChange) 1121 if err != nil { 1122 return nil, nil, err 1123 } 1124 tasksets = finalizeUpdate(st, tasksets, len(updates) > 0, updated, userID, flags) 1125 return updated, tasksets, nil 1126 } 1127 1128 func doUpdate(ctx context.Context, st *state.State, names []string, updates []*snap.Info, params func(*snap.Info) (*RevisionOptions, Flags, *SnapState), userID int, globalFlags *Flags, deviceCtx DeviceContext, fromChange string) ([]string, []*state.TaskSet, error) { 1129 if globalFlags == nil { 1130 globalFlags = &Flags{} 1131 } 1132 1133 tasksets := make([]*state.TaskSet, 0, len(updates)+2) // 1 for auto-aliases, 1 for re-refresh 1134 1135 refreshAll := len(names) == 0 1136 var nameSet map[string]bool 1137 if len(names) != 0 { 1138 nameSet = make(map[string]bool, len(names)) 1139 for _, name := range names { 1140 nameSet[name] = true 1141 } 1142 } 1143 1144 newAutoAliases, mustPruneAutoAliases, transferTargets, err := autoAliasesUpdate(st, names, updates) 1145 if err != nil { 1146 return nil, nil, err 1147 } 1148 1149 reportUpdated := make(map[string]bool, len(updates)) 1150 var pruningAutoAliasesTs *state.TaskSet 1151 1152 if len(mustPruneAutoAliases) != 0 { 1153 var err error 1154 pruningAutoAliasesTs, err = applyAutoAliasesDelta(st, mustPruneAutoAliases, "prune", refreshAll, fromChange, func(snapName string, _ *state.TaskSet) { 1155 if nameSet[snapName] { 1156 reportUpdated[snapName] = true 1157 } 1158 }) 1159 if err != nil { 1160 return nil, nil, err 1161 } 1162 tasksets = append(tasksets, pruningAutoAliasesTs) 1163 } 1164 1165 // wait for the auto-alias prune tasks as needed 1166 scheduleUpdate := func(snapName string, ts *state.TaskSet) { 1167 if pruningAutoAliasesTs != nil && (mustPruneAutoAliases[snapName] != nil || transferTargets[snapName]) { 1168 ts.WaitAll(pruningAutoAliasesTs) 1169 } 1170 reportUpdated[snapName] = true 1171 } 1172 1173 // first snapd, core, bases, then rest 1174 sort.Stable(snap.ByType(updates)) 1175 prereqs := make(map[string]*state.TaskSet) 1176 waitPrereq := func(ts *state.TaskSet, prereqName string) { 1177 preTs := prereqs[prereqName] 1178 if preTs != nil { 1179 ts.WaitAll(preTs) 1180 } 1181 } 1182 1183 // updates is sorted by kind so this will process first core 1184 // and bases and then other snaps 1185 for _, update := range updates { 1186 revnoOpts, flags, snapst := params(update) 1187 flags.IsAutoRefresh = globalFlags.IsAutoRefresh 1188 1189 flags, err := ensureInstallPreconditions(st, update, flags, snapst, deviceCtx) 1190 if err != nil { 1191 if refreshAll { 1192 logger.Noticef("cannot update %q: %v", update.InstanceName(), err) 1193 continue 1194 } 1195 return nil, nil, err 1196 } 1197 1198 if err := earlyEpochCheck(update, snapst); err != nil { 1199 if refreshAll { 1200 logger.Noticef("cannot update %q: %v", update.InstanceName(), err) 1201 continue 1202 } 1203 return nil, nil, err 1204 } 1205 1206 snapUserID, err := userIDForSnap(st, snapst, userID) 1207 if err != nil { 1208 return nil, nil, err 1209 } 1210 1211 snapsup := &SnapSetup{ 1212 Base: update.Base, 1213 Prereq: defaultContentPlugProviders(st, update), 1214 Channel: revnoOpts.Channel, 1215 CohortKey: revnoOpts.CohortKey, 1216 UserID: snapUserID, 1217 Flags: flags.ForSnapSetup(), 1218 DownloadInfo: &update.DownloadInfo, 1219 SideInfo: &update.SideInfo, 1220 Type: update.Type(), 1221 PlugsOnly: len(update.Slots) == 0, 1222 InstanceKey: update.InstanceKey, 1223 auxStoreInfo: auxStoreInfo{ 1224 Website: update.Website, 1225 Media: update.Media, 1226 }, 1227 } 1228 1229 ts, err := doInstall(st, snapst, snapsup, 0, fromChange, inUseFor(deviceCtx)) 1230 if err != nil { 1231 if refreshAll { 1232 // doing "refresh all", just skip this snap 1233 logger.Noticef("cannot refresh snap %q: %v", update.InstanceName(), err) 1234 continue 1235 } 1236 return nil, nil, err 1237 } 1238 ts.JoinLane(st.NewLane()) 1239 1240 // because of the sorting of updates we fill prereqs 1241 // first (if branch) and only then use it to setup 1242 // waits (else branch) 1243 if t := update.Type(); t == snap.TypeOS || t == snap.TypeBase || t == snap.TypeSnapd { 1244 // prereq types come first in updates, we 1245 // also assume bases don't have hooks, otherwise 1246 // they would need to wait on core or snapd 1247 prereqs[update.InstanceName()] = ts 1248 } else { 1249 // prereqs were processed already, wait for 1250 // them as necessary for the other kind of 1251 // snaps 1252 waitPrereq(ts, defaultCoreSnapName) 1253 waitPrereq(ts, "snapd") 1254 if update.Base != "" { 1255 waitPrereq(ts, update.Base) 1256 } 1257 } 1258 1259 scheduleUpdate(update.InstanceName(), ts) 1260 tasksets = append(tasksets, ts) 1261 } 1262 1263 if len(newAutoAliases) != 0 { 1264 addAutoAliasesTs, err := applyAutoAliasesDelta(st, newAutoAliases, "refresh", refreshAll, fromChange, scheduleUpdate) 1265 if err != nil { 1266 return nil, nil, err 1267 } 1268 tasksets = append(tasksets, addAutoAliasesTs) 1269 } 1270 1271 updated := make([]string, 0, len(reportUpdated)) 1272 for name := range reportUpdated { 1273 updated = append(updated, name) 1274 } 1275 1276 return updated, tasksets, nil 1277 } 1278 1279 func finalizeUpdate(st *state.State, tasksets []*state.TaskSet, hasUpdates bool, updated []string, userID int, globalFlags *Flags) []*state.TaskSet { 1280 if hasUpdates && !globalFlags.NoReRefresh { 1281 // re-refresh will check the lanes to decide what to 1282 // _actually_ re-refresh, but it'll be a subset of updated 1283 // (and equal to updated if nothing goes wrong) 1284 rerefresh := st.NewTask("check-rerefresh", fmt.Sprintf("Consider re-refresh of %s", strutil.Quoted(updated))) 1285 rerefresh.Set("rerefresh-setup", reRefreshSetup{ 1286 UserID: userID, 1287 Flags: globalFlags, 1288 }) 1289 tasksets = append(tasksets, state.NewTaskSet(rerefresh)) 1290 } 1291 return tasksets 1292 } 1293 1294 func applyAutoAliasesDelta(st *state.State, delta map[string][]string, op string, refreshAll bool, fromChange string, linkTs func(instanceName string, ts *state.TaskSet)) (*state.TaskSet, error) { 1295 applyTs := state.NewTaskSet() 1296 kind := "refresh-aliases" 1297 msg := i18n.G("Refresh aliases for snap %q") 1298 if op == "prune" { 1299 kind = "prune-auto-aliases" 1300 msg = i18n.G("Prune automatic aliases for snap %q") 1301 } 1302 for instanceName, aliases := range delta { 1303 if err := checkChangeConflictIgnoringOneChange(st, instanceName, nil, fromChange); err != nil { 1304 if refreshAll { 1305 // doing "refresh all", just skip this snap 1306 logger.Noticef("cannot %s automatic aliases for snap %q: %v", op, instanceName, err) 1307 continue 1308 } 1309 return nil, err 1310 } 1311 1312 snapName, instanceKey := snap.SplitInstanceName(instanceName) 1313 snapsup := &SnapSetup{ 1314 SideInfo: &snap.SideInfo{RealName: snapName}, 1315 InstanceKey: instanceKey, 1316 } 1317 alias := st.NewTask(kind, fmt.Sprintf(msg, snapsup.InstanceName())) 1318 alias.Set("snap-setup", &snapsup) 1319 if op == "prune" { 1320 alias.Set("aliases", aliases) 1321 } 1322 ts := state.NewTaskSet(alias) 1323 linkTs(instanceName, ts) 1324 applyTs.AddAll(ts) 1325 } 1326 return applyTs, nil 1327 } 1328 1329 func autoAliasesUpdate(st *state.State, names []string, updates []*snap.Info) (changed map[string][]string, mustPrune map[string][]string, transferTargets map[string]bool, err error) { 1330 changed, dropped, err := autoAliasesDelta(st, nil) 1331 if err != nil { 1332 if len(names) != 0 { 1333 // not "refresh all", error 1334 return nil, nil, nil, err 1335 } 1336 // log and continue 1337 logger.Noticef("cannot find the delta for automatic aliases for some snaps: %v", err) 1338 } 1339 1340 refreshAll := len(names) == 0 1341 1342 // dropped alias -> snapName 1343 droppedAliases := make(map[string][]string, len(dropped)) 1344 for instanceName, aliases := range dropped { 1345 for _, alias := range aliases { 1346 droppedAliases[alias] = append(droppedAliases[alias], instanceName) 1347 } 1348 } 1349 1350 // filter changed considering only names if set: 1351 // we add auto-aliases only for mentioned snaps 1352 if !refreshAll && len(changed) != 0 { 1353 filteredChanged := make(map[string][]string, len(changed)) 1354 for _, name := range names { 1355 if changed[name] != nil { 1356 filteredChanged[name] = changed[name] 1357 } 1358 } 1359 changed = filteredChanged 1360 } 1361 1362 // mark snaps that are sources or target of transfers 1363 transferSources := make(map[string]bool, len(dropped)) 1364 transferTargets = make(map[string]bool, len(changed)) 1365 for instanceName, aliases := range changed { 1366 for _, alias := range aliases { 1367 if sources := droppedAliases[alias]; len(sources) != 0 { 1368 transferTargets[instanceName] = true 1369 for _, source := range sources { 1370 transferSources[source] = true 1371 } 1372 } 1373 } 1374 } 1375 1376 // snaps with updates 1377 updating := make(map[string]bool, len(updates)) 1378 for _, info := range updates { 1379 updating[info.InstanceName()] = true 1380 } 1381 1382 // add explicitly auto-aliases only for snaps that are not updated 1383 for instanceName := range changed { 1384 if updating[instanceName] { 1385 delete(changed, instanceName) 1386 } 1387 } 1388 1389 // prune explicitly auto-aliases only for snaps that are mentioned 1390 // and not updated OR the source of transfers 1391 mustPrune = make(map[string][]string, len(dropped)) 1392 for instanceName := range transferSources { 1393 mustPrune[instanceName] = dropped[instanceName] 1394 } 1395 if refreshAll { 1396 for instanceName, aliases := range dropped { 1397 if !updating[instanceName] { 1398 mustPrune[instanceName] = aliases 1399 } 1400 } 1401 } else { 1402 for _, name := range names { 1403 if !updating[name] && dropped[name] != nil { 1404 mustPrune[name] = dropped[name] 1405 } 1406 } 1407 } 1408 1409 return changed, mustPrune, transferTargets, nil 1410 } 1411 1412 // resolveChannel returns the effective channel to use, based on the requested 1413 // channel and constrains set by device model, or an error if switching to 1414 // requested channel is forbidden. 1415 func resolveChannel(st *state.State, snapName, oldChannel, newChannel string, deviceCtx DeviceContext) (effectiveChannel string, err error) { 1416 if newChannel == "" { 1417 return "", nil 1418 } 1419 1420 // ensure we do not switch away from the kernel-track in the model 1421 model := deviceCtx.Model() 1422 1423 var pinnedTrack, which string 1424 if snapName == model.Kernel() && model.KernelTrack() != "" { 1425 pinnedTrack, which = model.KernelTrack(), "kernel" 1426 } 1427 if snapName == model.Gadget() && model.GadgetTrack() != "" { 1428 pinnedTrack, which = model.GadgetTrack(), "gadget" 1429 } 1430 1431 if pinnedTrack == "" { 1432 // no pinned track 1433 return channel.Resolve(oldChannel, newChannel) 1434 } 1435 1436 // channel name is valid and consist of risk level or 1437 // risk/branch only, do the right thing and default to risk (or 1438 // risk/branch) within the pinned track 1439 resChannel, err := channel.ResolvePinned(pinnedTrack, newChannel) 1440 if err == channel.ErrPinnedTrackSwitch { 1441 // switching to a different track is not allowed 1442 return "", fmt.Errorf("cannot switch from %s track %q as specified for the (device) model to %q", which, pinnedTrack, newChannel) 1443 1444 } 1445 if err != nil { 1446 return "", err 1447 } 1448 return resChannel, nil 1449 } 1450 1451 var errRevisionSwitch = errors.New("cannot switch revision") 1452 1453 func switchSummary(snap, chanFrom, chanTo, cohFrom, cohTo string) string { 1454 if cohFrom != cohTo { 1455 if cohTo == "" { 1456 // leave cohort 1457 if chanFrom == chanTo { 1458 return fmt.Sprintf(i18n.G("Switch snap %q away from cohort %q"), 1459 snap, strutil.ElliptLeft(cohFrom, 10)) 1460 } 1461 if chanFrom == "" { 1462 return fmt.Sprintf(i18n.G("Switch snap %q to channel %q and away from cohort %q"), 1463 snap, chanTo, strutil.ElliptLeft(cohFrom, 10), 1464 ) 1465 } 1466 return fmt.Sprintf(i18n.G("Switch snap %q from channel %q to %q and away from cohort %q"), 1467 snap, chanFrom, chanTo, strutil.ElliptLeft(cohFrom, 10), 1468 ) 1469 } 1470 if cohFrom == "" { 1471 // moving into a cohort 1472 if chanFrom == chanTo { 1473 return fmt.Sprintf(i18n.G("Switch snap %q from no cohort to %q"), 1474 snap, strutil.ElliptLeft(cohTo, 10)) 1475 } 1476 if chanFrom == "" { 1477 return fmt.Sprintf(i18n.G("Switch snap %q to channel %q and from no cohort to %q"), 1478 snap, chanTo, strutil.ElliptLeft(cohTo, 10), 1479 ) 1480 } 1481 // chanTo == "" is not interesting 1482 return fmt.Sprintf(i18n.G("Switch snap %q from channel %q to %q and from no cohort to %q"), 1483 snap, chanFrom, chanTo, strutil.ElliptLeft(cohTo, 10), 1484 ) 1485 } 1486 if chanFrom == chanTo { 1487 return fmt.Sprintf(i18n.G("Switch snap %q from cohort %q to %q"), 1488 snap, strutil.ElliptLeft(cohFrom, 10), strutil.ElliptLeft(cohTo, 10)) 1489 } 1490 if chanFrom == "" { 1491 return fmt.Sprintf(i18n.G("Switch snap %q to channel %q and from cohort %q to %q"), 1492 snap, chanTo, strutil.ElliptLeft(cohFrom, 10), strutil.ElliptLeft(cohTo, 10), 1493 ) 1494 } 1495 return fmt.Sprintf(i18n.G("Switch snap %q from channel %q to %q and from cohort %q to %q"), 1496 snap, chanFrom, chanTo, 1497 strutil.ElliptLeft(cohFrom, 10), strutil.ElliptLeft(cohTo, 10), 1498 ) 1499 } 1500 1501 if chanFrom == "" { 1502 return fmt.Sprintf(i18n.G("Switch snap %q to channel %q"), 1503 snap, chanTo) 1504 } 1505 if chanFrom != chanTo { 1506 return fmt.Sprintf(i18n.G("Switch snap %q from channel %q to %q"), 1507 snap, chanFrom, chanTo) 1508 } 1509 // a no-change switch is accepted for idempotency 1510 return "No change switch (no-op)" 1511 } 1512 1513 // Switch switches a snap to a new channel and/or cohort 1514 func Switch(st *state.State, name string, opts *RevisionOptions) (*state.TaskSet, error) { 1515 if opts == nil { 1516 opts = &RevisionOptions{} 1517 } 1518 if !opts.Revision.Unset() { 1519 return nil, errRevisionSwitch 1520 } 1521 var snapst SnapState 1522 err := Get(st, name, &snapst) 1523 if err != nil && err != state.ErrNoState { 1524 return nil, err 1525 } 1526 if !snapst.IsInstalled() { 1527 return nil, &snap.NotInstalledError{Snap: name} 1528 } 1529 1530 if err := CheckChangeConflict(st, name, nil); err != nil { 1531 return nil, err 1532 } 1533 1534 deviceCtx, err := DeviceCtxFromState(st, nil) 1535 if err != nil { 1536 return nil, err 1537 } 1538 1539 opts.Channel, err = resolveChannel(st, name, snapst.TrackingChannel, opts.Channel, deviceCtx) 1540 if err != nil { 1541 return nil, err 1542 } 1543 1544 snapsup := &SnapSetup{ 1545 SideInfo: snapst.CurrentSideInfo(), 1546 InstanceKey: snapst.InstanceKey, 1547 // set the from state (i.e. no change), they are overridden from opts as needed below 1548 CohortKey: snapst.CohortKey, 1549 Channel: snapst.TrackingChannel, 1550 } 1551 1552 if opts.Channel != "" { 1553 snapsup.Channel = opts.Channel 1554 } 1555 if opts.CohortKey != "" { 1556 snapsup.CohortKey = opts.CohortKey 1557 } 1558 if opts.LeaveCohort { 1559 snapsup.CohortKey = "" 1560 } 1561 1562 summary := switchSummary(snapsup.InstanceName(), snapst.TrackingChannel, snapsup.Channel, snapst.CohortKey, snapsup.CohortKey) 1563 switchSnap := st.NewTask("switch-snap", summary) 1564 switchSnap.Set("snap-setup", &snapsup) 1565 1566 return state.NewTaskSet(switchSnap), nil 1567 } 1568 1569 // RevisionOptions control the selection of a snap revision. 1570 type RevisionOptions struct { 1571 Channel string 1572 Revision snap.Revision 1573 CohortKey string 1574 LeaveCohort bool 1575 } 1576 1577 // Update initiates a change updating a snap. 1578 // Note that the state must be locked by the caller. 1579 // 1580 // The returned TaskSet will contain a DownloadAndChecksDoneEdge. 1581 func Update(st *state.State, name string, opts *RevisionOptions, userID int, flags Flags) (*state.TaskSet, error) { 1582 return UpdateWithDeviceContext(st, name, opts, userID, flags, nil, "") 1583 } 1584 1585 // UpdateWithDeviceContext initiates a change updating a snap. 1586 // It will query for the snap with the given deviceCtx. 1587 // Note that the state must be locked by the caller. 1588 // 1589 // The returned TaskSet will contain a DownloadAndChecksDoneEdge. 1590 func UpdateWithDeviceContext(st *state.State, name string, opts *RevisionOptions, userID int, flags Flags, deviceCtx DeviceContext, fromChange string) (*state.TaskSet, error) { 1591 if opts == nil { 1592 opts = &RevisionOptions{} 1593 } 1594 var snapst SnapState 1595 err := Get(st, name, &snapst) 1596 if err != nil && err != state.ErrNoState { 1597 return nil, err 1598 } 1599 if !snapst.IsInstalled() { 1600 return nil, &snap.NotInstalledError{Snap: name} 1601 } 1602 1603 // FIXME: snaps that are not active are skipped for now 1604 // until we know what we want to do 1605 if !snapst.Active { 1606 return nil, fmt.Errorf("refreshing disabled snap %q not supported", name) 1607 } 1608 1609 // need to have a model set before trying to talk the store 1610 deviceCtx, err = DevicePastSeeding(st, deviceCtx) 1611 if err != nil { 1612 return nil, err 1613 } 1614 1615 opts.Channel, err = resolveChannel(st, name, snapst.TrackingChannel, opts.Channel, deviceCtx) 1616 if err != nil { 1617 return nil, err 1618 } 1619 1620 if opts.Channel == "" { 1621 // default to tracking the same channel 1622 opts.Channel = snapst.TrackingChannel 1623 } 1624 if opts.CohortKey == "" { 1625 // default to being in the same cohort 1626 opts.CohortKey = snapst.CohortKey 1627 } 1628 if opts.LeaveCohort { 1629 opts.CohortKey = "" 1630 } 1631 1632 // TODO: make flags be per revision to avoid this logic (that 1633 // leaves corner cases all over the place) 1634 if !(flags.JailMode || flags.DevMode) { 1635 flags.Classic = flags.Classic || snapst.Flags.Classic 1636 } 1637 1638 var updates []*snap.Info 1639 info, infoErr := infoForUpdate(st, &snapst, name, opts, userID, flags, deviceCtx) 1640 switch infoErr { 1641 case nil: 1642 updates = append(updates, info) 1643 case store.ErrNoUpdateAvailable: 1644 // there may be some new auto-aliases 1645 default: 1646 return nil, infoErr 1647 } 1648 1649 tr := config.NewTransaction(st) 1650 checkDiskSpaceRefresh, err := features.Flag(tr, features.CheckDiskSpaceRefresh) 1651 if err != nil && !config.IsNoOption(err) { 1652 return nil, err 1653 } 1654 if checkDiskSpaceRefresh { 1655 // check if there is enough disk space for requested snap and its 1656 // prerequisites. 1657 totalSize, err := installSize(st, updates, userID) 1658 if err != nil { 1659 return nil, err 1660 } 1661 requiredSpace := safetyMarginDiskSpace(totalSize) 1662 path := dirs.SnapdStateDir(dirs.GlobalRootDir) 1663 if err := osutilCheckFreeSpace(path, requiredSpace); err != nil { 1664 snaps := make([]string, len(updates)) 1665 for i, up := range updates { 1666 snaps[i] = up.InstanceName() 1667 } 1668 if _, ok := err.(*osutil.NotEnoughDiskSpaceError); ok { 1669 return nil, &InsufficientSpaceError{ 1670 Path: path, 1671 Snaps: snaps, 1672 ChangeKind: "refresh", 1673 } 1674 } 1675 return nil, err 1676 } 1677 } 1678 1679 params := func(update *snap.Info) (*RevisionOptions, Flags, *SnapState) { 1680 return opts, flags, &snapst 1681 } 1682 1683 _, tts, err := doUpdate(context.TODO(), st, []string{name}, updates, params, userID, &flags, deviceCtx, fromChange) 1684 if err != nil { 1685 return nil, err 1686 } 1687 1688 // see if we need to switch the channel or cohort, or toggle ignore-validation 1689 switchChannel := snapst.TrackingChannel != opts.Channel 1690 switchCohortKey := snapst.CohortKey != opts.CohortKey 1691 toggleIgnoreValidation := snapst.IgnoreValidation != flags.IgnoreValidation 1692 if infoErr == store.ErrNoUpdateAvailable && (switchChannel || switchCohortKey || toggleIgnoreValidation) { 1693 if err := checkChangeConflictIgnoringOneChange(st, name, nil, fromChange); err != nil { 1694 return nil, err 1695 } 1696 1697 snapsup := &SnapSetup{ 1698 SideInfo: snapst.CurrentSideInfo(), 1699 Flags: snapst.Flags.ForSnapSetup(), 1700 InstanceKey: snapst.InstanceKey, 1701 CohortKey: opts.CohortKey, 1702 } 1703 1704 if switchChannel || switchCohortKey { 1705 // update the tracked channel and cohort 1706 snapsup.Channel = opts.Channel 1707 snapsup.CohortKey = opts.CohortKey 1708 // Update the current snap channel as well. This ensures that 1709 // the UI displays the right values. 1710 snapsup.SideInfo.Channel = opts.Channel 1711 1712 summary := switchSummary(snapsup.InstanceName(), snapst.TrackingChannel, opts.Channel, snapst.CohortKey, opts.CohortKey) 1713 switchSnap := st.NewTask("switch-snap-channel", summary) 1714 switchSnap.Set("snap-setup", &snapsup) 1715 1716 switchSnapTs := state.NewTaskSet(switchSnap) 1717 for _, ts := range tts { 1718 switchSnapTs.WaitAll(ts) 1719 } 1720 tts = append(tts, switchSnapTs) 1721 } 1722 1723 if toggleIgnoreValidation { 1724 snapsup.IgnoreValidation = flags.IgnoreValidation 1725 toggle := st.NewTask("toggle-snap-flags", fmt.Sprintf(i18n.G("Toggle snap %q flags"), snapsup.InstanceName())) 1726 toggle.Set("snap-setup", &snapsup) 1727 1728 toggleTs := state.NewTaskSet(toggle) 1729 for _, ts := range tts { 1730 toggleTs.WaitAll(ts) 1731 } 1732 tts = append(tts, toggleTs) 1733 } 1734 } 1735 1736 if len(tts) == 0 && len(updates) == 0 { 1737 // really nothing to do, return the original no-update-available error 1738 return nil, infoErr 1739 } 1740 1741 tts = finalizeUpdate(st, tts, len(updates) > 0, []string{name}, userID, &flags) 1742 1743 flat := state.NewTaskSet() 1744 for _, ts := range tts { 1745 // The tasksets we get from "doUpdate" contain important 1746 // "TaskEdge" information that is needed for "Remodel". 1747 // To preserve those we need to use "AddAllWithEdges()". 1748 if err := flat.AddAllWithEdges(ts); err != nil { 1749 return nil, err 1750 } 1751 } 1752 return flat, nil 1753 } 1754 1755 func infoForUpdate(st *state.State, snapst *SnapState, name string, opts *RevisionOptions, userID int, flags Flags, deviceCtx DeviceContext) (*snap.Info, error) { 1756 if opts.Revision.Unset() { 1757 // good ol' refresh 1758 info, err := updateInfo(st, snapst, opts, userID, flags, deviceCtx) 1759 if err != nil { 1760 return nil, err 1761 } 1762 if ValidateRefreshes != nil && !flags.IgnoreValidation { 1763 _, err := ValidateRefreshes(st, []*snap.Info{info}, nil, userID, deviceCtx) 1764 if err != nil { 1765 return nil, err 1766 } 1767 } 1768 return info, nil 1769 } 1770 var sideInfo *snap.SideInfo 1771 for _, si := range snapst.Sequence { 1772 if si.Revision == opts.Revision { 1773 sideInfo = si 1774 break 1775 } 1776 } 1777 if sideInfo == nil { 1778 // refresh from given revision from store 1779 return updateToRevisionInfo(st, snapst, opts.Revision, userID, deviceCtx) 1780 } 1781 1782 // refresh-to-local, this assumes the snap revision is mounted 1783 return readInfo(name, sideInfo, errorOnBroken) 1784 } 1785 1786 // AutoRefreshAssertions allows to hook fetching of important assertions 1787 // into the Autorefresh function. 1788 var AutoRefreshAssertions func(st *state.State, userID int) error 1789 1790 // AutoRefresh is the wrapper that will do a refresh of all the installed 1791 // snaps on the system. In addition to that it will also refresh important 1792 // assertions. 1793 func AutoRefresh(ctx context.Context, st *state.State) ([]string, []*state.TaskSet, error) { 1794 userID := 0 1795 1796 if AutoRefreshAssertions != nil { 1797 if err := AutoRefreshAssertions(st, userID); err != nil { 1798 return nil, nil, err 1799 } 1800 } 1801 1802 return UpdateMany(ctx, st, nil, userID, &Flags{IsAutoRefresh: true}) 1803 } 1804 1805 // LinkNewBaseOrKernel will create prepare/link-snap tasks for a remodel 1806 func LinkNewBaseOrKernel(st *state.State, name string) (*state.TaskSet, error) { 1807 var snapst SnapState 1808 err := Get(st, name, &snapst) 1809 if err == state.ErrNoState { 1810 return nil, &snap.NotInstalledError{Snap: name} 1811 } 1812 if err != nil { 1813 return nil, err 1814 } 1815 1816 if err := CheckChangeConflict(st, name, nil); err != nil { 1817 return nil, err 1818 } 1819 1820 info, err := snapst.CurrentInfo() 1821 if err != nil { 1822 return nil, err 1823 } 1824 1825 switch info.Type() { 1826 case snap.TypeOS, snap.TypeBase, snap.TypeKernel: 1827 // good 1828 default: 1829 // bad 1830 return nil, fmt.Errorf("cannot link type %v", info.Type()) 1831 } 1832 1833 snapsup := &SnapSetup{ 1834 SideInfo: snapst.CurrentSideInfo(), 1835 Flags: snapst.Flags.ForSnapSetup(), 1836 Type: info.Type(), 1837 PlugsOnly: len(info.Slots) == 0, 1838 InstanceKey: snapst.InstanceKey, 1839 } 1840 1841 prepareSnap := st.NewTask("prepare-snap", fmt.Sprintf(i18n.G("Prepare snap %q (%s) for remodel"), snapsup.InstanceName(), snapst.Current)) 1842 prepareSnap.Set("snap-setup", &snapsup) 1843 1844 linkSnap := st.NewTask("link-snap", fmt.Sprintf(i18n.G("Make snap %q (%s) available to the system during remodel"), snapsup.InstanceName(), snapst.Current)) 1845 linkSnap.Set("snap-setup-task", prepareSnap.ID()) 1846 linkSnap.WaitFor(prepareSnap) 1847 1848 // we need this for remodel 1849 ts := state.NewTaskSet(prepareSnap, linkSnap) 1850 ts.MarkEdge(prepareSnap, DownloadAndChecksDoneEdge) 1851 return ts, nil 1852 } 1853 1854 // Enable sets a snap to the active state 1855 func Enable(st *state.State, name string) (*state.TaskSet, error) { 1856 var snapst SnapState 1857 err := Get(st, name, &snapst) 1858 if err == state.ErrNoState { 1859 return nil, &snap.NotInstalledError{Snap: name} 1860 } 1861 if err != nil { 1862 return nil, err 1863 } 1864 1865 if snapst.Active { 1866 return nil, fmt.Errorf("snap %q already enabled", name) 1867 } 1868 1869 if err := CheckChangeConflict(st, name, nil); err != nil { 1870 return nil, err 1871 } 1872 1873 info, err := snapst.CurrentInfo() 1874 if err != nil { 1875 return nil, err 1876 } 1877 1878 snapsup := &SnapSetup{ 1879 SideInfo: snapst.CurrentSideInfo(), 1880 Flags: snapst.Flags.ForSnapSetup(), 1881 Type: info.Type(), 1882 PlugsOnly: len(info.Slots) == 0, 1883 InstanceKey: snapst.InstanceKey, 1884 } 1885 1886 prepareSnap := st.NewTask("prepare-snap", fmt.Sprintf(i18n.G("Prepare snap %q (%s)"), snapsup.InstanceName(), snapst.Current)) 1887 prepareSnap.Set("snap-setup", &snapsup) 1888 1889 setupProfiles := st.NewTask("setup-profiles", fmt.Sprintf(i18n.G("Setup snap %q (%s) security profiles"), snapsup.InstanceName(), snapst.Current)) 1890 setupProfiles.Set("snap-setup-task", prepareSnap.ID()) 1891 setupProfiles.WaitFor(prepareSnap) 1892 1893 linkSnap := st.NewTask("link-snap", fmt.Sprintf(i18n.G("Make snap %q (%s) available to the system"), snapsup.InstanceName(), snapst.Current)) 1894 linkSnap.Set("snap-setup-task", prepareSnap.ID()) 1895 linkSnap.WaitFor(setupProfiles) 1896 1897 // setup aliases 1898 setupAliases := st.NewTask("setup-aliases", fmt.Sprintf(i18n.G("Setup snap %q aliases"), snapsup.InstanceName())) 1899 setupAliases.Set("snap-setup-task", prepareSnap.ID()) 1900 setupAliases.WaitFor(linkSnap) 1901 1902 startSnapServices := st.NewTask("start-snap-services", fmt.Sprintf(i18n.G("Start snap %q (%s) services"), snapsup.InstanceName(), snapst.Current)) 1903 startSnapServices.Set("snap-setup-task", prepareSnap.ID()) 1904 startSnapServices.WaitFor(setupAliases) 1905 1906 return state.NewTaskSet(prepareSnap, setupProfiles, linkSnap, setupAliases, startSnapServices), nil 1907 } 1908 1909 // Disable sets a snap to the inactive state 1910 func Disable(st *state.State, name string) (*state.TaskSet, error) { 1911 var snapst SnapState 1912 err := Get(st, name, &snapst) 1913 if err == state.ErrNoState { 1914 return nil, &snap.NotInstalledError{Snap: name} 1915 } 1916 if err != nil { 1917 return nil, err 1918 } 1919 if !snapst.Active { 1920 return nil, fmt.Errorf("snap %q already disabled", name) 1921 } 1922 1923 info, err := Info(st, name, snapst.Current) 1924 if err != nil { 1925 return nil, err 1926 } 1927 if !canDisable(info) { 1928 return nil, fmt.Errorf("snap %q cannot be disabled", name) 1929 } 1930 1931 if err := CheckChangeConflict(st, name, nil); err != nil { 1932 return nil, err 1933 } 1934 1935 snapsup := &SnapSetup{ 1936 SideInfo: &snap.SideInfo{ 1937 RealName: snap.InstanceSnap(name), 1938 Revision: snapst.Current, 1939 }, 1940 Type: info.Type(), 1941 PlugsOnly: len(info.Slots) == 0, 1942 InstanceKey: snapst.InstanceKey, 1943 } 1944 1945 stopSnapServices := st.NewTask("stop-snap-services", fmt.Sprintf(i18n.G("Stop snap %q (%s) services"), snapsup.InstanceName(), snapst.Current)) 1946 stopSnapServices.Set("snap-setup", &snapsup) 1947 stopSnapServices.Set("stop-reason", snap.StopReasonDisable) 1948 1949 removeAliases := st.NewTask("remove-aliases", fmt.Sprintf(i18n.G("Remove aliases for snap %q"), snapsup.InstanceName())) 1950 removeAliases.Set("snap-setup-task", stopSnapServices.ID()) 1951 removeAliases.WaitFor(stopSnapServices) 1952 1953 unlinkSnap := st.NewTask("unlink-snap", fmt.Sprintf(i18n.G("Make snap %q (%s) unavailable to the system"), snapsup.InstanceName(), snapst.Current)) 1954 unlinkSnap.Set("snap-setup-task", stopSnapServices.ID()) 1955 unlinkSnap.WaitFor(removeAliases) 1956 1957 removeProfiles := st.NewTask("remove-profiles", fmt.Sprintf(i18n.G("Remove security profiles of snap %q"), snapsup.InstanceName())) 1958 removeProfiles.Set("snap-setup-task", stopSnapServices.ID()) 1959 removeProfiles.WaitFor(unlinkSnap) 1960 1961 return state.NewTaskSet(stopSnapServices, removeAliases, unlinkSnap, removeProfiles), nil 1962 } 1963 1964 // canDisable verifies that a snap can be deactivated. 1965 func canDisable(si *snap.Info) bool { 1966 for _, importantSnapType := range []snap.Type{snap.TypeGadget, snap.TypeKernel, snap.TypeOS} { 1967 if importantSnapType == si.Type() { 1968 return false 1969 } 1970 } 1971 1972 return true 1973 } 1974 1975 // canRemove verifies that a snap can be removed. 1976 func canRemove(st *state.State, si *snap.Info, snapst *SnapState, removeAll bool, deviceCtx DeviceContext) error { 1977 rev := snap.Revision{} 1978 if !removeAll { 1979 rev = si.Revision 1980 } 1981 1982 return PolicyFor(si.Type(), deviceCtx.Model()).CanRemove(st, snapst, rev, deviceCtx) 1983 } 1984 1985 // RemoveFlags are used to pass additional flags to the Remove operation. 1986 type RemoveFlags struct { 1987 // Remove the snap without creating snapshot data 1988 Purge bool 1989 } 1990 1991 // Remove returns a set of tasks for removing snap. 1992 // Note that the state must be locked by the caller. 1993 func Remove(st *state.State, name string, revision snap.Revision, flags *RemoveFlags) (*state.TaskSet, error) { 1994 ts, snapshotSize, err := removeTasks(st, name, revision, flags) 1995 // removeTasks() checks check-disk-space-remove feature flag, so snapshotSize 1996 // will only be greater than 0 if the feature is enabled. 1997 if snapshotSize > 0 { 1998 requiredSpace := safetyMarginDiskSpace(snapshotSize) 1999 path := dirs.SnapdStateDir(dirs.GlobalRootDir) 2000 if err := osutilCheckFreeSpace(path, requiredSpace); err != nil { 2001 if _, ok := err.(*osutil.NotEnoughDiskSpaceError); ok { 2002 return nil, &InsufficientSpaceError{ 2003 Path: path, 2004 Snaps: []string{name}, 2005 ChangeKind: "remove", 2006 Message: fmt.Sprintf("cannot create automatic snapshot when removing last revision of the snap: %v", err)} 2007 } 2008 return nil, err 2009 } 2010 } 2011 return ts, err 2012 } 2013 2014 // removeTasks provides the task set to remove snap name after taking a snapshot 2015 // if flags.Purge is not true, it also computes an estimate of the latter size. 2016 func removeTasks(st *state.State, name string, revision snap.Revision, flags *RemoveFlags) (removeTs *state.TaskSet, snapshotSize uint64, err error) { 2017 var snapst SnapState 2018 err = Get(st, name, &snapst) 2019 if err != nil && err != state.ErrNoState { 2020 return nil, 0, err 2021 } 2022 2023 if !snapst.IsInstalled() { 2024 return nil, 0, &snap.NotInstalledError{Snap: name, Rev: snap.R(0)} 2025 } 2026 2027 if err := CheckChangeConflict(st, name, nil); err != nil { 2028 return nil, 0, err 2029 } 2030 2031 deviceCtx, err := DeviceCtxFromState(st, nil) 2032 if err != nil { 2033 return nil, 0, err 2034 } 2035 2036 active := snapst.Active 2037 var removeAll bool 2038 if revision.Unset() { 2039 revision = snapst.Current 2040 removeAll = true 2041 } else { 2042 if active { 2043 if revision == snapst.Current { 2044 msg := "cannot remove active revision %s of snap %q" 2045 if len(snapst.Sequence) > 1 { 2046 msg += " (revert first?)" 2047 } 2048 return nil, 0, fmt.Errorf(msg, revision, name) 2049 } 2050 active = false 2051 } 2052 2053 if !revisionInSequence(&snapst, revision) { 2054 return nil, 0, &snap.NotInstalledError{Snap: name, Rev: revision} 2055 } 2056 2057 removeAll = len(snapst.Sequence) == 1 2058 } 2059 2060 info, err := Info(st, name, revision) 2061 if err != nil { 2062 return nil, 0, err 2063 } 2064 2065 // check if this is something that can be removed 2066 if err := canRemove(st, info, &snapst, removeAll, deviceCtx); err != nil { 2067 return nil, 0, fmt.Errorf("snap %q is not removable: %v", name, err) 2068 } 2069 2070 // main/current SnapSetup 2071 snapsup := SnapSetup{ 2072 SideInfo: &snap.SideInfo{ 2073 SnapID: info.SnapID, 2074 RealName: snap.InstanceSnap(name), 2075 Revision: revision, 2076 }, 2077 Type: info.Type(), 2078 PlugsOnly: len(info.Slots) == 0, 2079 InstanceKey: snapst.InstanceKey, 2080 } 2081 2082 // trigger remove 2083 2084 removeTs = state.NewTaskSet() 2085 var chain *state.TaskSet 2086 2087 addNext := func(ts *state.TaskSet) { 2088 if chain != nil { 2089 ts.WaitAll(chain) 2090 } 2091 removeTs.AddAll(ts) 2092 chain = ts 2093 } 2094 2095 var prev *state.Task 2096 var stopSnapServices *state.Task 2097 if active { 2098 stopSnapServices = st.NewTask("stop-snap-services", fmt.Sprintf(i18n.G("Stop snap %q services"), name)) 2099 stopSnapServices.Set("snap-setup", snapsup) 2100 stopSnapServices.Set("stop-reason", snap.StopReasonRemove) 2101 addNext(state.NewTaskSet(stopSnapServices)) 2102 prev = stopSnapServices 2103 } 2104 2105 // only run remove hook if uninstalling the snap completely 2106 if removeAll { 2107 removeHook := SetupRemoveHook(st, snapsup.InstanceName()) 2108 addNext(state.NewTaskSet(removeHook)) 2109 prev = removeHook 2110 2111 // run disconnect hooks 2112 disconnect := st.NewTask("auto-disconnect", fmt.Sprintf(i18n.G("Disconnect interfaces of snap %q"), snapsup.InstanceName())) 2113 disconnect.Set("snap-setup", snapsup) 2114 if prev != nil { 2115 disconnect.WaitFor(prev) 2116 } 2117 addNext(state.NewTaskSet(disconnect)) 2118 prev = disconnect 2119 } 2120 2121 // 'purge' flag disables automatic snapshot for given remove op 2122 if flags == nil || !flags.Purge { 2123 if tp, _ := snapst.Type(); tp == snap.TypeApp && removeAll { 2124 ts, err := AutomaticSnapshot(st, name) 2125 if err == nil { 2126 tr := config.NewTransaction(st) 2127 checkDiskSpaceRemove, err := features.Flag(tr, features.CheckDiskSpaceRemove) 2128 if err != nil && !config.IsNoOption(err) { 2129 return nil, 0, err 2130 } 2131 if checkDiskSpaceRemove { 2132 snapshotSize, err = EstimateSnapshotSize(st, name, nil) 2133 if err != nil { 2134 return nil, 0, err 2135 } 2136 } 2137 addNext(ts) 2138 } else { 2139 if err != ErrNothingToDo { 2140 return nil, 0, err 2141 } 2142 } 2143 } 2144 } 2145 2146 if active { // unlink 2147 var tasks []*state.Task 2148 2149 removeAliases := st.NewTask("remove-aliases", fmt.Sprintf(i18n.G("Remove aliases for snap %q"), name)) 2150 removeAliases.WaitFor(prev) // prev is not needed beyond here 2151 removeAliases.Set("snap-setup-task", stopSnapServices.ID()) 2152 2153 unlink := st.NewTask("unlink-snap", fmt.Sprintf(i18n.G("Make snap %q unavailable to the system"), name)) 2154 unlink.Set("snap-setup-task", stopSnapServices.ID()) 2155 unlink.WaitFor(removeAliases) 2156 2157 removeSecurity := st.NewTask("remove-profiles", fmt.Sprintf(i18n.G("Remove security profile for snap %q (%s)"), name, revision)) 2158 removeSecurity.WaitFor(unlink) 2159 removeSecurity.Set("snap-setup-task", stopSnapServices.ID()) 2160 2161 tasks = append(tasks, removeAliases, unlink, removeSecurity) 2162 addNext(state.NewTaskSet(tasks...)) 2163 } 2164 2165 if removeAll { 2166 seq := snapst.Sequence 2167 currentIndex := snapst.LastIndex(snapst.Current) 2168 for i := len(seq) - 1; i >= 0; i-- { 2169 if i != currentIndex { 2170 si := seq[i] 2171 addNext(removeInactiveRevision(st, name, info.SnapID, si.Revision)) 2172 } 2173 } 2174 // add tasks for removing the current revision last, 2175 // this is then also when common data will be removed 2176 if currentIndex >= 0 { 2177 addNext(removeInactiveRevision(st, name, info.SnapID, seq[currentIndex].Revision)) 2178 } 2179 } else { 2180 addNext(removeInactiveRevision(st, name, info.SnapID, revision)) 2181 } 2182 2183 return removeTs, snapshotSize, nil 2184 } 2185 2186 func removeInactiveRevision(st *state.State, name, snapID string, revision snap.Revision) *state.TaskSet { 2187 snapName, instanceKey := snap.SplitInstanceName(name) 2188 snapsup := SnapSetup{ 2189 SideInfo: &snap.SideInfo{ 2190 RealName: snapName, 2191 SnapID: snapID, 2192 Revision: revision, 2193 }, 2194 InstanceKey: instanceKey, 2195 } 2196 2197 clearData := st.NewTask("clear-snap", fmt.Sprintf(i18n.G("Remove data for snap %q (%s)"), name, revision)) 2198 clearData.Set("snap-setup", snapsup) 2199 2200 discardSnap := st.NewTask("discard-snap", fmt.Sprintf(i18n.G("Remove snap %q (%s) from the system"), name, revision)) 2201 discardSnap.WaitFor(clearData) 2202 discardSnap.Set("snap-setup-task", clearData.ID()) 2203 2204 return state.NewTaskSet(clearData, discardSnap) 2205 } 2206 2207 // RemoveMany removes everything from the given list of names. 2208 // Note that the state must be locked by the caller. 2209 func RemoveMany(st *state.State, names []string) ([]string, []*state.TaskSet, error) { 2210 removed := make([]string, 0, len(names)) 2211 tasksets := make([]*state.TaskSet, 0, len(names)) 2212 2213 var totalSnapshotsSize uint64 2214 path := dirs.SnapdStateDir(dirs.GlobalRootDir) 2215 2216 for _, name := range names { 2217 ts, snapshotSize, err := removeTasks(st, name, snap.R(0), nil) 2218 // FIXME: is this expected behavior? 2219 if _, ok := err.(*snap.NotInstalledError); ok { 2220 continue 2221 } 2222 if err != nil { 2223 return nil, nil, err 2224 } 2225 totalSnapshotsSize += snapshotSize 2226 removed = append(removed, name) 2227 ts.JoinLane(st.NewLane()) 2228 tasksets = append(tasksets, ts) 2229 } 2230 2231 // removeTasks() checks check-disk-space-remove feature flag, so totalSnapshotsSize 2232 // will only be greater than 0 if the feature is enabled. 2233 if totalSnapshotsSize > 0 { 2234 requiredSpace := safetyMarginDiskSpace(totalSnapshotsSize) 2235 if err := osutilCheckFreeSpace(path, requiredSpace); err != nil { 2236 if _, ok := err.(*osutil.NotEnoughDiskSpaceError); ok { 2237 return nil, nil, &InsufficientSpaceError{ 2238 Path: path, 2239 Snaps: names, 2240 ChangeKind: "remove", 2241 } 2242 } 2243 return nil, nil, err 2244 } 2245 } 2246 2247 return removed, tasksets, nil 2248 } 2249 2250 // Revert returns a set of tasks for reverting to the previous version of the snap. 2251 // Note that the state must be locked by the caller. 2252 func Revert(st *state.State, name string, flags Flags) (*state.TaskSet, error) { 2253 var snapst SnapState 2254 err := Get(st, name, &snapst) 2255 if err != nil && err != state.ErrNoState { 2256 return nil, err 2257 } 2258 2259 pi := snapst.previousSideInfo() 2260 if pi == nil { 2261 return nil, fmt.Errorf("no revision to revert to") 2262 } 2263 2264 return RevertToRevision(st, name, pi.Revision, flags) 2265 } 2266 2267 func RevertToRevision(st *state.State, name string, rev snap.Revision, flags Flags) (*state.TaskSet, error) { 2268 var snapst SnapState 2269 err := Get(st, name, &snapst) 2270 if err != nil && err != state.ErrNoState { 2271 return nil, err 2272 } 2273 2274 if snapst.Current == rev { 2275 return nil, fmt.Errorf("already on requested revision") 2276 } 2277 2278 if !snapst.Active { 2279 return nil, fmt.Errorf("cannot revert inactive snaps") 2280 } 2281 i := snapst.LastIndex(rev) 2282 if i < 0 { 2283 return nil, fmt.Errorf("cannot find revision %s for snap %q", rev, name) 2284 } 2285 2286 flags.Revert = true 2287 // TODO: make flags be per revision to avoid this logic (that 2288 // leaves corner cases all over the place) 2289 if !(flags.JailMode || flags.DevMode || flags.Classic) { 2290 if snapst.Flags.DevMode { 2291 flags.DevMode = true 2292 } 2293 if snapst.Flags.JailMode { 2294 flags.JailMode = true 2295 } 2296 if snapst.Flags.Classic { 2297 flags.Classic = true 2298 } 2299 } 2300 2301 info, err := Info(st, name, rev) 2302 if err != nil { 2303 return nil, err 2304 } 2305 2306 snapsup := &SnapSetup{ 2307 Base: info.Base, 2308 SideInfo: snapst.Sequence[i], 2309 Flags: flags.ForSnapSetup(), 2310 Type: info.Type(), 2311 PlugsOnly: len(info.Slots) == 0, 2312 InstanceKey: snapst.InstanceKey, 2313 } 2314 return doInstall(st, &snapst, snapsup, 0, "", nil) 2315 } 2316 2317 // TransitionCore transitions from an old core snap name to a new core 2318 // snap name. It is used for the ubuntu-core -> core transition (that 2319 // is not just a rename because the two snaps have different snapIDs) 2320 // 2321 // Note that this function makes some assumptions like: 2322 // - no aliases setup for both snaps 2323 // - no data needs to be copied 2324 // - all interfaces are absolutely identical on both new and old 2325 // Do not use this as a general way to transition from snap A to snap B. 2326 func TransitionCore(st *state.State, oldName, newName string) ([]*state.TaskSet, error) { 2327 var oldSnapst, newSnapst SnapState 2328 err := Get(st, oldName, &oldSnapst) 2329 if err != nil && err != state.ErrNoState { 2330 return nil, err 2331 } 2332 if !oldSnapst.IsInstalled() { 2333 return nil, fmt.Errorf("cannot transition snap %q: not installed", oldName) 2334 } 2335 2336 var all []*state.TaskSet 2337 // install new core (if not already installed) 2338 err = Get(st, newName, &newSnapst) 2339 if err != nil && err != state.ErrNoState { 2340 return nil, err 2341 } 2342 if !newSnapst.IsInstalled() { 2343 var userID int 2344 newInfo, err := installInfo(context.TODO(), st, newName, &RevisionOptions{Channel: oldSnapst.TrackingChannel}, userID, nil) 2345 if err != nil { 2346 return nil, err 2347 } 2348 2349 // start by installing the new snap 2350 tsInst, err := doInstall(st, &newSnapst, &SnapSetup{ 2351 Channel: oldSnapst.TrackingChannel, 2352 DownloadInfo: &newInfo.DownloadInfo, 2353 SideInfo: &newInfo.SideInfo, 2354 Type: newInfo.Type(), 2355 }, 0, "", nil) 2356 if err != nil { 2357 return nil, err 2358 } 2359 all = append(all, tsInst) 2360 } 2361 2362 // then transition the interface connections over 2363 transIf := st.NewTask("transition-ubuntu-core", fmt.Sprintf(i18n.G("Transition security profiles from %q to %q"), oldName, newName)) 2364 transIf.Set("old-name", oldName) 2365 transIf.Set("new-name", newName) 2366 if len(all) > 0 { 2367 transIf.WaitAll(all[0]) 2368 } 2369 tsTrans := state.NewTaskSet(transIf) 2370 all = append(all, tsTrans) 2371 2372 // FIXME: this is just here for the tests 2373 transIf.Set("snap-setup", &SnapSetup{ 2374 SideInfo: &snap.SideInfo{ 2375 RealName: oldName, 2376 }, 2377 }) 2378 2379 // then remove the old snap 2380 tsRm, err := Remove(st, oldName, snap.R(0), nil) 2381 if err != nil { 2382 return nil, err 2383 } 2384 tsRm.WaitFor(transIf) 2385 all = append(all, tsRm) 2386 2387 return all, nil 2388 } 2389 2390 // State/info accessors 2391 2392 // Installing returns whether there's an in-progress installation. 2393 func Installing(st *state.State) bool { 2394 for _, task := range st.Tasks() { 2395 k := task.Kind() 2396 chg := task.Change() 2397 if k == "mount-snap" && chg != nil && !chg.Status().Ready() { 2398 return true 2399 } 2400 } 2401 return false 2402 } 2403 2404 // Info returns the information about the snap with given name and revision. 2405 // Works also for a mounted candidate snap in the process of being installed. 2406 func Info(st *state.State, name string, revision snap.Revision) (*snap.Info, error) { 2407 var snapst SnapState 2408 err := Get(st, name, &snapst) 2409 if err == state.ErrNoState { 2410 return nil, &snap.NotInstalledError{Snap: name} 2411 } 2412 if err != nil { 2413 return nil, err 2414 } 2415 2416 for i := len(snapst.Sequence) - 1; i >= 0; i-- { 2417 if si := snapst.Sequence[i]; si.Revision == revision { 2418 return readInfo(name, si, 0) 2419 } 2420 } 2421 2422 return nil, fmt.Errorf("cannot find snap %q at revision %s", name, revision.String()) 2423 } 2424 2425 // CurrentInfo returns the information about the current revision of a snap with the given name. 2426 func CurrentInfo(st *state.State, name string) (*snap.Info, error) { 2427 var snapst SnapState 2428 err := Get(st, name, &snapst) 2429 if err != nil && err != state.ErrNoState { 2430 return nil, err 2431 } 2432 info, err := snapst.CurrentInfo() 2433 if err == ErrNoCurrent { 2434 return nil, &snap.NotInstalledError{Snap: name} 2435 } 2436 return info, err 2437 } 2438 2439 // Get retrieves the SnapState of the given snap. 2440 func Get(st *state.State, name string, snapst *SnapState) error { 2441 if snapst == nil { 2442 return fmt.Errorf("internal error: snapst is nil") 2443 } 2444 // SnapState is (un-)marshalled from/to JSON, fields having omitempty 2445 // tag will not appear in the output (if empty) and subsequently will 2446 // not be unmarshalled to (or cleared); if the caller reuses the same 2447 // struct though subsequent calls, it is possible that they end up with 2448 // garbage inside, clear the destination struct so that we always 2449 // unmarshal to a clean state 2450 *snapst = SnapState{} 2451 2452 var snaps map[string]*json.RawMessage 2453 err := st.Get("snaps", &snaps) 2454 if err != nil { 2455 return err 2456 } 2457 raw, ok := snaps[name] 2458 if !ok { 2459 return state.ErrNoState 2460 } 2461 2462 // XXX: &snapst pointer isn't needed here but it is likely historical 2463 // (a bug in old JSON marshaling probably). 2464 err = json.Unmarshal([]byte(*raw), &snapst) 2465 if err != nil { 2466 return fmt.Errorf("cannot unmarshal snap state: %v", err) 2467 } 2468 return nil 2469 } 2470 2471 // All retrieves return a map from name to SnapState for all current snaps in the system state. 2472 func All(st *state.State) (map[string]*SnapState, error) { 2473 // XXX: result is a map because sideloaded snaps carry no name 2474 // atm in their sideinfos 2475 var stateMap map[string]*SnapState 2476 if err := st.Get("snaps", &stateMap); err != nil && err != state.ErrNoState { 2477 return nil, err 2478 } 2479 curStates := make(map[string]*SnapState, len(stateMap)) 2480 for instanceName, snapst := range stateMap { 2481 curStates[instanceName] = snapst 2482 } 2483 return curStates, nil 2484 } 2485 2486 // NumSnaps returns the number of installed snaps. 2487 func NumSnaps(st *state.State) (int, error) { 2488 var snaps map[string]*json.RawMessage 2489 if err := st.Get("snaps", &snaps); err != nil && err != state.ErrNoState { 2490 return -1, err 2491 } 2492 return len(snaps), nil 2493 } 2494 2495 // Set sets the SnapState of the given snap, overwriting any earlier state. 2496 // Note that a SnapState with an empty Sequence will be treated as if snapst was 2497 // nil and name will be deleted from the state. 2498 func Set(st *state.State, name string, snapst *SnapState) { 2499 var snaps map[string]*json.RawMessage 2500 err := st.Get("snaps", &snaps) 2501 if err != nil && err != state.ErrNoState { 2502 panic("internal error: cannot unmarshal snaps state: " + err.Error()) 2503 } 2504 if snaps == nil { 2505 snaps = make(map[string]*json.RawMessage) 2506 } 2507 if snapst == nil || (len(snapst.Sequence) == 0) { 2508 delete(snaps, name) 2509 } else { 2510 data, err := json.Marshal(snapst) 2511 if err != nil { 2512 panic("internal error: cannot marshal snap state: " + err.Error()) 2513 } 2514 raw := json.RawMessage(data) 2515 snaps[name] = &raw 2516 } 2517 st.Set("snaps", snaps) 2518 } 2519 2520 // ActiveInfos returns information about all active snaps. 2521 func ActiveInfos(st *state.State) ([]*snap.Info, error) { 2522 var stateMap map[string]*SnapState 2523 var infos []*snap.Info 2524 if err := st.Get("snaps", &stateMap); err != nil && err != state.ErrNoState { 2525 return nil, err 2526 } 2527 for instanceName, snapst := range stateMap { 2528 if !snapst.Active { 2529 continue 2530 } 2531 snapInfo, err := snapst.CurrentInfo() 2532 if err != nil { 2533 logger.Noticef("cannot retrieve info for snap %q: %s", instanceName, err) 2534 continue 2535 } 2536 infos = append(infos, snapInfo) 2537 } 2538 return infos, nil 2539 } 2540 2541 func HasSnapOfType(st *state.State, snapType snap.Type) (bool, error) { 2542 var stateMap map[string]*SnapState 2543 if err := st.Get("snaps", &stateMap); err != nil && err != state.ErrNoState { 2544 return false, err 2545 } 2546 2547 for _, snapst := range stateMap { 2548 typ, err := snapst.Type() 2549 if err != nil { 2550 return false, err 2551 } 2552 if typ == snapType { 2553 return true, nil 2554 } 2555 } 2556 2557 return false, nil 2558 } 2559 2560 func infosForType(st *state.State, snapType snap.Type) ([]*snap.Info, error) { 2561 var stateMap map[string]*SnapState 2562 if err := st.Get("snaps", &stateMap); err != nil && err != state.ErrNoState { 2563 return nil, err 2564 } 2565 2566 var res []*snap.Info 2567 for _, snapst := range stateMap { 2568 if !snapst.IsInstalled() { 2569 continue 2570 } 2571 typ, err := snapst.Type() 2572 if err != nil { 2573 return nil, err 2574 } 2575 if typ != snapType { 2576 continue 2577 } 2578 si, err := snapst.CurrentInfo() 2579 if err != nil { 2580 return nil, err 2581 } 2582 res = append(res, si) 2583 } 2584 2585 if len(res) == 0 { 2586 return nil, state.ErrNoState 2587 } 2588 2589 return res, nil 2590 } 2591 2592 func infoForDeviceSnap(st *state.State, deviceCtx DeviceContext, which string, whichName func(*asserts.Model) string) (*snap.Info, error) { 2593 if deviceCtx == nil { 2594 return nil, fmt.Errorf("internal error: unset deviceCtx") 2595 } 2596 model := deviceCtx.Model() 2597 snapName := whichName(model) 2598 if snapName == "" { 2599 return nil, state.ErrNoState 2600 } 2601 var snapst SnapState 2602 err := Get(st, snapName, &snapst) 2603 if err != nil { 2604 return nil, err 2605 } 2606 return snapst.CurrentInfo() 2607 } 2608 2609 // GadgetInfo finds the gadget snap's info for the given device context. 2610 func GadgetInfo(st *state.State, deviceCtx DeviceContext) (*snap.Info, error) { 2611 return infoForDeviceSnap(st, deviceCtx, "gadget", (*asserts.Model).Gadget) 2612 } 2613 2614 // KernelInfo finds the kernel snap's info for the given device context. 2615 func KernelInfo(st *state.State, deviceCtx DeviceContext) (*snap.Info, error) { 2616 return infoForDeviceSnap(st, deviceCtx, "kernel", (*asserts.Model).Kernel) 2617 } 2618 2619 // BootBaseInfo finds the boot base snap's info for the given device context. 2620 func BootBaseInfo(st *state.State, deviceCtx DeviceContext) (*snap.Info, error) { 2621 baseName := func(mod *asserts.Model) string { 2622 base := mod.Base() 2623 if base == "" { 2624 return "core" 2625 } 2626 return base 2627 } 2628 return infoForDeviceSnap(st, deviceCtx, "boot base", baseName) 2629 } 2630 2631 // TODO: reintroduce a KernelInfo(state.State, DeviceContext) if needed 2632 // KernelInfo finds the current kernel snap's info. 2633 2634 // coreInfo finds the current OS snap's info. If both 2635 // "core" and "ubuntu-core" is installed then "core" 2636 // is preferred. Different core names are not supported 2637 // currently and will result in an error. 2638 func coreInfo(st *state.State) (*snap.Info, error) { 2639 res, err := infosForType(st, snap.TypeOS) 2640 if err != nil { 2641 return nil, err 2642 } 2643 2644 // a single core: just return it 2645 if len(res) == 1 { 2646 return res[0], nil 2647 } 2648 2649 // some systems have two cores: ubuntu-core/core 2650 // we always return "core" in this case 2651 if len(res) == 2 { 2652 if res[0].InstanceName() == defaultCoreSnapName && res[1].InstanceName() == "ubuntu-core" { 2653 return res[0], nil 2654 } 2655 if res[0].InstanceName() == "ubuntu-core" && res[1].InstanceName() == defaultCoreSnapName { 2656 return res[1], nil 2657 } 2658 return nil, fmt.Errorf("unexpected cores %q and %q", res[0].InstanceName(), res[1].InstanceName()) 2659 } 2660 2661 return nil, fmt.Errorf("unexpected number of cores, got %d", len(res)) 2662 } 2663 2664 // ConfigDefaults returns the configuration defaults for the snap as 2665 // specified in the gadget for the given device context. 2666 // If gadget is absent or the snap has no snap-id it returns 2667 // ErrNoState. 2668 func ConfigDefaults(st *state.State, deviceCtx DeviceContext, snapName string) (map[string]interface{}, error) { 2669 info, err := GadgetInfo(st, deviceCtx) 2670 if err != nil { 2671 return nil, err 2672 } 2673 2674 // system configuration is kept under "core" so apply its defaults when 2675 // configuring "core" 2676 isSystemDefaults := snapName == defaultCoreSnapName 2677 var snapst SnapState 2678 if err := Get(st, snapName, &snapst); err != nil && err != state.ErrNoState { 2679 return nil, err 2680 } 2681 2682 var snapID string 2683 if snapst.IsInstalled() { 2684 snapID = snapst.CurrentSideInfo().SnapID 2685 } 2686 // system snaps (core and snapd) snaps can be addressed even without a 2687 // snap-id via the special "system" value in the config; first-boot 2688 // always configures the core snap with UseConfigDefaults 2689 if snapID == "" && !isSystemDefaults { 2690 return nil, state.ErrNoState 2691 } 2692 2693 // no constraints enforced: those should have been checked before already 2694 gadgetInfo, err := gadget.ReadInfo(info.MountDir(), nil) 2695 if err != nil { 2696 return nil, err 2697 } 2698 2699 // we support setting core defaults via "system" 2700 if isSystemDefaults { 2701 if defaults, ok := gadgetInfo.Defaults["system"]; ok { 2702 if _, ok := gadgetInfo.Defaults[snapID]; ok && snapID != "" { 2703 logger.Noticef("core snap configuration defaults found under both 'system' key and core-snap-id, preferring 'system'") 2704 } 2705 2706 return defaults, nil 2707 } 2708 } 2709 2710 defaults, ok := gadgetInfo.Defaults[snapID] 2711 if !ok { 2712 return nil, state.ErrNoState 2713 } 2714 2715 return defaults, nil 2716 } 2717 2718 // GadgetConnections returns the interface connection instructions 2719 // specified in the gadget for the given device context. 2720 // If gadget is absent it returns ErrNoState. 2721 func GadgetConnections(st *state.State, deviceCtx DeviceContext) ([]gadget.Connection, error) { 2722 info, err := GadgetInfo(st, deviceCtx) 2723 if err != nil { 2724 return nil, err 2725 } 2726 2727 // no constraints enforced: those should have been checked before already 2728 gadgetInfo, err := gadget.ReadInfo(info.MountDir(), nil) 2729 if err != nil { 2730 return nil, err 2731 } 2732 2733 return gadgetInfo.Connections, nil 2734 } 2735 2736 func MockOsutilCheckFreeSpace(mock func(path string, minSize uint64) error) (restore func()) { 2737 old := osutilCheckFreeSpace 2738 osutilCheckFreeSpace = mock 2739 return func() { osutilCheckFreeSpace = old } 2740 }