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