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