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  }