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  }