mirror of
https://github.com/token2/snapd.git
synced 2026-03-13 11:15:47 -07:00
195 lines
5.9 KiB
Go
195 lines
5.9 KiB
Go
// -*- Mode: Go; indent-tabs-mode: t -*-
|
|
|
|
/*
|
|
* Copyright (C) 2016-2018 Canonical Ltd
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 3 as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
package patch
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/snapcore/snapd/logger"
|
|
"github.com/snapcore/snapd/overlord/state"
|
|
"github.com/snapcore/snapd/snapdtool"
|
|
)
|
|
|
|
// Level is the current implemented patch level of the state format and content.
|
|
var Level = 6
|
|
|
|
// Sublevel is the current implemented sublevel for the Level.
|
|
// Sublevel 0 is the first patch for the new Level, rollback below x.0 is not possible.
|
|
// Sublevel patches > 0 do not prevent rollbacks.
|
|
var Sublevel = 3
|
|
|
|
type PatchFunc func(s *state.State) error
|
|
|
|
// patches maps from patch level L to the list of sublevel patches.
|
|
var patches = make(map[int][]PatchFunc)
|
|
|
|
// Init initializes an empty state to the current implemented patch level.
|
|
func Init(s *state.State) {
|
|
s.Lock()
|
|
defer s.Unlock()
|
|
if err := s.Get("patch-level", new(int)); !errors.Is(err, state.ErrNoState) {
|
|
panic("internal error: expected empty state, attempting to override patch-level without actual patching")
|
|
}
|
|
s.Set("patch-level", Level)
|
|
|
|
if err := s.Get("patch-sublevel", new(int)); !errors.Is(err, state.ErrNoState) {
|
|
panic("internal error: expected empty state, attempting to override patch-sublevel without actual patching")
|
|
}
|
|
s.Set("patch-sublevel", Sublevel)
|
|
}
|
|
|
|
// applySublevelPatches applies all sublevel patches for given level, starting
|
|
// from firstSublevel index.
|
|
func applySublevelPatches(level, firstSublevel int, s *state.State) error {
|
|
for sublevel := firstSublevel; sublevel < len(patches[level]); sublevel++ {
|
|
if sublevel > 0 {
|
|
logger.Noticef("Patching system state level %d to sublevel %d...", level, sublevel)
|
|
}
|
|
err := applyOne(patches[level][sublevel], s, level, sublevel)
|
|
if err != nil {
|
|
logger.Noticef("Cannot patch: %v", err)
|
|
return fmt.Errorf("cannot patch system state to level %d, sublevel %d: %v", level, sublevel, err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// maybeResetSublevelForLevel60 checks if we're coming from a different version
|
|
// of snapd and if so, reset sublevel back to 0 to re-apply sublevel patches.
|
|
func maybeResetSublevelForLevel60(s *state.State, sublevel *int) error {
|
|
s.Lock()
|
|
defer s.Unlock()
|
|
|
|
var lastVersion string
|
|
err := s.Get("patch-sublevel-last-version", &lastVersion)
|
|
if err != nil && !errors.Is(err, state.ErrNoState) {
|
|
return err
|
|
}
|
|
if errors.Is(err, state.ErrNoState) || lastVersion != snapdtool.Version {
|
|
*sublevel = 0
|
|
s.Set("patch-sublevel", *sublevel)
|
|
// unset old reset key in case of revert into old version.
|
|
// TODO: this can go away if we go through a snapd epoch.
|
|
s.Set("patch-sublevel-reset", nil)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Apply applies any necessary patches to update the provided state to
|
|
// conventions required by the current patch level of the system.
|
|
func Apply(s *state.State) error {
|
|
var stateLevel, stateSublevel int
|
|
s.Lock()
|
|
err := s.Get("patch-level", &stateLevel)
|
|
if err == nil || errors.Is(err, state.ErrNoState) {
|
|
err = s.Get("patch-sublevel", &stateSublevel)
|
|
}
|
|
s.Unlock()
|
|
|
|
if err != nil && !errors.Is(err, state.ErrNoState) {
|
|
return err
|
|
}
|
|
|
|
if stateLevel > Level {
|
|
return fmt.Errorf("cannot downgrade: snapd is too old for the current system state (patch level %d)", stateLevel)
|
|
}
|
|
|
|
// check if we refreshed from 6.0 which was not aware of sublevels
|
|
if stateLevel == 6 && stateSublevel > 0 {
|
|
if err := maybeResetSublevelForLevel60(s, &stateSublevel); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if stateLevel == Level && stateSublevel == Sublevel {
|
|
return nil
|
|
}
|
|
|
|
// downgrade within same level; update sublevel in the state so that sublevel patches
|
|
// are re-applied if the user refreshes to a newer patch sublevel again.
|
|
if stateLevel == Level && stateSublevel > Sublevel {
|
|
s.Lock()
|
|
s.Set("patch-sublevel", Sublevel)
|
|
s.Unlock()
|
|
return nil
|
|
}
|
|
|
|
// apply any missing sublevel patches for current state level before upgrading to new levels.
|
|
// the 0th sublevel patch is a patch for major level update (e.g. 7.0),
|
|
// therefore there is +1 for the indices.
|
|
if stateSublevel+1 < len(patches[stateLevel]) {
|
|
if err := applySublevelPatches(stateLevel, stateSublevel+1, s); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// at the lower Level - apply all new level and sublevel patches
|
|
for level := stateLevel + 1; level <= Level; level++ {
|
|
sublevels := patches[level]
|
|
logger.Noticef("Patching system state from level %d to %d", level-1, level)
|
|
if sublevels == nil {
|
|
return fmt.Errorf("cannot upgrade: snapd is too new for the current system state (patch level %d)", level-1)
|
|
}
|
|
if err := applySublevelPatches(level, 0, s); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
s.Lock()
|
|
// store last snapd version last in case system is restarted before patches are applied
|
|
s.Set("patch-sublevel-last-version", snapdtool.Version)
|
|
s.Unlock()
|
|
|
|
return nil
|
|
}
|
|
|
|
func applyOne(patch func(s *state.State) error, s *state.State, newLevel, newSublevel int) error {
|
|
s.Lock()
|
|
defer s.Unlock()
|
|
|
|
err := patch(s)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
s.Set("patch-level", newLevel)
|
|
s.Set("patch-sublevel", newSublevel)
|
|
return nil
|
|
}
|
|
|
|
// Mock mocks the current patch level and available patches.
|
|
func Mock(level int, sublevel int, p map[int][]PatchFunc) (restore func()) {
|
|
oldLevel := Level
|
|
oldPatches := patches
|
|
Level = level
|
|
patches = p
|
|
|
|
oldSublevel := Sublevel
|
|
Sublevel = sublevel
|
|
|
|
return func() {
|
|
Level = oldLevel
|
|
patches = oldPatches
|
|
Sublevel = oldSublevel
|
|
}
|
|
}
|