github.com/david-imola/snapd@v0.0.0-20210611180407-2de8ddeece6d/overlord/patch/patch.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 patch 21 22 import ( 23 "fmt" 24 25 "github.com/snapcore/snapd/logger" 26 "github.com/snapcore/snapd/overlord/state" 27 "github.com/snapcore/snapd/snapdtool" 28 ) 29 30 // Level is the current implemented patch level of the state format and content. 31 var Level = 6 32 33 // Sublevel is the current implemented sublevel for the Level. 34 // Sublevel 0 is the first patch for the new Level, rollback below x.0 is not possible. 35 // Sublevel patches > 0 do not prevent rollbacks. 36 var Sublevel = 3 37 38 type PatchFunc func(s *state.State) error 39 40 // patches maps from patch level L to the list of sublevel patches. 41 var patches = make(map[int][]PatchFunc) 42 43 // Init initializes an empty state to the current implemented patch level. 44 func Init(s *state.State) { 45 s.Lock() 46 defer s.Unlock() 47 if s.Get("patch-level", new(int)) != state.ErrNoState { 48 panic("internal error: expected empty state, attempting to override patch-level without actual patching") 49 } 50 s.Set("patch-level", Level) 51 52 if s.Get("patch-sublevel", new(int)) != state.ErrNoState { 53 panic("internal error: expected empty state, attempting to override patch-sublevel without actual patching") 54 } 55 s.Set("patch-sublevel", Sublevel) 56 } 57 58 // applySublevelPatches applies all sublevel patches for given level, starting 59 // from firstSublevel index. 60 func applySublevelPatches(level, firstSublevel int, s *state.State) error { 61 for sublevel := firstSublevel; sublevel < len(patches[level]); sublevel++ { 62 if sublevel > 0 { 63 logger.Noticef("Patching system state level %d to sublevel %d...", level, sublevel) 64 } 65 err := applyOne(patches[level][sublevel], s, level, sublevel) 66 if err != nil { 67 logger.Noticef("Cannot patch: %v", err) 68 return fmt.Errorf("cannot patch system state to level %d, sublevel %d: %v", level, sublevel, err) 69 } 70 } 71 return nil 72 } 73 74 // maybeResetSublevelForLevel60 checks if we're coming from a different version 75 // of snapd and if so, reset sublevel back to 0 to re-apply sublevel patches. 76 func maybeResetSublevelForLevel60(s *state.State, sublevel *int) error { 77 s.Lock() 78 defer s.Unlock() 79 80 var lastVersion string 81 err := s.Get("patch-sublevel-last-version", &lastVersion) 82 if err != nil && err != state.ErrNoState { 83 return err 84 } 85 if err == state.ErrNoState || lastVersion != snapdtool.Version { 86 *sublevel = 0 87 s.Set("patch-sublevel", *sublevel) 88 // unset old reset key in case of revert into old version. 89 // TODO: this can go away if we go through a snapd epoch. 90 s.Set("patch-sublevel-reset", nil) 91 } 92 93 return nil 94 } 95 96 // Apply applies any necessary patches to update the provided state to 97 // conventions required by the current patch level of the system. 98 func Apply(s *state.State) error { 99 var stateLevel, stateSublevel int 100 s.Lock() 101 err := s.Get("patch-level", &stateLevel) 102 if err == nil || err == state.ErrNoState { 103 err = s.Get("patch-sublevel", &stateSublevel) 104 } 105 s.Unlock() 106 107 if err != nil && err != state.ErrNoState { 108 return err 109 } 110 111 if stateLevel > Level { 112 return fmt.Errorf("cannot downgrade: snapd is too old for the current system state (patch level %d)", stateLevel) 113 } 114 115 // check if we refreshed from 6.0 which was not aware of sublevels 116 if stateLevel == 6 && stateSublevel > 0 { 117 if err := maybeResetSublevelForLevel60(s, &stateSublevel); err != nil { 118 return err 119 } 120 } 121 122 if stateLevel == Level && stateSublevel == Sublevel { 123 return nil 124 } 125 126 // downgrade within same level; update sublevel in the state so that sublevel patches 127 // are re-applied if the user refreshes to a newer patch sublevel again. 128 if stateLevel == Level && stateSublevel > Sublevel { 129 s.Lock() 130 s.Set("patch-sublevel", Sublevel) 131 s.Unlock() 132 return nil 133 } 134 135 // apply any missing sublevel patches for current state level before upgrading to new levels. 136 // the 0th sublevel patch is a patch for major level update (e.g. 7.0), 137 // therefore there is +1 for the indices. 138 if stateSublevel+1 < len(patches[stateLevel]) { 139 if err := applySublevelPatches(stateLevel, stateSublevel+1, s); err != nil { 140 return err 141 } 142 } 143 144 // at the lower Level - apply all new level and sublevel patches 145 for level := stateLevel + 1; level <= Level; level++ { 146 sublevels := patches[level] 147 logger.Noticef("Patching system state from level %d to %d", level-1, level) 148 if sublevels == nil { 149 return fmt.Errorf("cannot upgrade: snapd is too new for the current system state (patch level %d)", level-1) 150 } 151 if err := applySublevelPatches(level, 0, s); err != nil { 152 return err 153 } 154 } 155 156 s.Lock() 157 // store last snapd version last in case system is restarted before patches are applied 158 s.Set("patch-sublevel-last-version", snapdtool.Version) 159 s.Unlock() 160 161 return nil 162 } 163 164 func applyOne(patch func(s *state.State) error, s *state.State, newLevel, newSublevel int) error { 165 s.Lock() 166 defer s.Unlock() 167 168 err := patch(s) 169 if err != nil { 170 return err 171 } 172 173 s.Set("patch-level", newLevel) 174 s.Set("patch-sublevel", newSublevel) 175 return nil 176 } 177 178 // Mock mocks the current patch level and available patches. 179 func Mock(level int, sublevel int, p map[int][]PatchFunc) (restore func()) { 180 oldLevel := Level 181 oldPatches := patches 182 Level = level 183 patches = p 184 185 oldSublevel := Sublevel 186 Sublevel = sublevel 187 188 return func() { 189 Level = oldLevel 190 patches = oldPatches 191 Sublevel = oldSublevel 192 } 193 }