github.com/stolowski/snapd@v0.0.0-20210407085831-115137ce5a22/overlord/devicestate/systems.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2020 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 devicestate
    21  
    22  import (
    23  	"fmt"
    24  
    25  	"github.com/snapcore/snapd/dirs"
    26  	"github.com/snapcore/snapd/overlord/snapstate"
    27  	"github.com/snapcore/snapd/overlord/state"
    28  	"github.com/snapcore/snapd/seed"
    29  )
    30  
    31  func checkSystemRequestConflict(st *state.State, systemLabel string) error {
    32  	st.Lock()
    33  	defer st.Unlock()
    34  
    35  	var seeded bool
    36  	if err := st.Get("seeded", &seeded); err != nil && err != state.ErrNoState {
    37  		return err
    38  	}
    39  	if seeded {
    40  		// the system is fully seeded already
    41  		return nil
    42  	}
    43  
    44  	// inspect the current system which is stored in modeenv, note we are
    45  	// holding the state lock so there is no race against mark-seeded
    46  	// clearing recovery system; recovery system is not cleared when seeding
    47  	// fails
    48  	modeEnv, err := maybeReadModeenv()
    49  	if err != nil {
    50  		return err
    51  	}
    52  	if modeEnv == nil {
    53  		// non UC20 systems do not support actions, no conflict can
    54  		// happen
    55  		return nil
    56  	}
    57  
    58  	// not yet fully seeded, hold off requests for the system that is being
    59  	// seeded, but allow requests for other systems
    60  	if modeEnv.RecoverySystem == systemLabel {
    61  		return &snapstate.ChangeConflictError{
    62  			ChangeKind: "seed",
    63  			Message:    "cannot request system action, system is seeding",
    64  		}
    65  	}
    66  	return nil
    67  }
    68  
    69  func systemFromSeed(label string, current *currentSystem) (*System, error) {
    70  	s, err := seed.Open(dirs.SnapSeedDir, label)
    71  	if err != nil {
    72  		return nil, fmt.Errorf("cannot open: %v", err)
    73  	}
    74  	if err := s.LoadAssertions(nil, nil); err != nil {
    75  		return nil, fmt.Errorf("cannot load assertions: %v", err)
    76  	}
    77  	// get the model
    78  	model := s.Model()
    79  	brand, err := s.Brand()
    80  	if err != nil {
    81  		return nil, fmt.Errorf("cannot obtain brand: %v", err)
    82  	}
    83  	system := &System{
    84  		Current: false,
    85  		Label:   label,
    86  		Model:   model,
    87  		Brand:   brand,
    88  		Actions: defaultSystemActions,
    89  	}
    90  	if current.sameAs(system) {
    91  		system.Current = true
    92  		system.Actions = current.actions
    93  	}
    94  	return system, nil
    95  }
    96  
    97  type currentSystem struct {
    98  	*seededSystem
    99  	actions []SystemAction
   100  }
   101  
   102  func (c *currentSystem) sameAs(other *System) bool {
   103  	return c != nil &&
   104  		c.System == other.Label &&
   105  		c.Model == other.Model.Model() &&
   106  		c.BrandID == other.Brand.AccountID()
   107  }
   108  
   109  func currentSystemForMode(st *state.State, mode string) (*currentSystem, error) {
   110  	var system *seededSystem
   111  	var actions []SystemAction
   112  	var err error
   113  
   114  	switch mode {
   115  	case "run":
   116  		actions = currentSystemActions
   117  		system, err = currentSeededSystem(st)
   118  	case "install":
   119  		// there is no current system for install mode
   120  		return nil, nil
   121  	case "recover":
   122  		actions = recoverSystemActions
   123  		// recover mode uses modeenv for reference
   124  		system, err = seededSystemFromModeenv()
   125  	default:
   126  		return nil, fmt.Errorf("internal error: cannot identify current system for unsupported mode %q", mode)
   127  	}
   128  	if err != nil {
   129  		return nil, err
   130  	}
   131  	currentSys := &currentSystem{
   132  		seededSystem: system,
   133  		actions:      actions,
   134  	}
   135  	return currentSys, nil
   136  }
   137  
   138  func currentSeededSystem(st *state.State) (*seededSystem, error) {
   139  	st.Lock()
   140  	defer st.Unlock()
   141  
   142  	var whatseeded []seededSystem
   143  	if err := st.Get("seeded-systems", &whatseeded); err != nil {
   144  		return nil, err
   145  	}
   146  	if len(whatseeded) == 0 {
   147  		// unexpected
   148  		return nil, state.ErrNoState
   149  	}
   150  	return &whatseeded[0], nil
   151  }
   152  
   153  func seededSystemFromModeenv() (*seededSystem, error) {
   154  	modeEnv, err := maybeReadModeenv()
   155  	if err != nil {
   156  		return nil, err
   157  	}
   158  	if modeEnv == nil {
   159  		return nil, fmt.Errorf("internal error: modeenv does not exist")
   160  	}
   161  	if modeEnv.RecoverySystem == "" {
   162  		return nil, fmt.Errorf("internal error: recovery system is unset")
   163  	}
   164  
   165  	system, err := systemFromSeed(modeEnv.RecoverySystem, nil)
   166  	if err != nil {
   167  		return nil, err
   168  	}
   169  	seededSys := &seededSystem{
   170  		System:    modeEnv.RecoverySystem,
   171  		Model:     system.Model.Model(),
   172  		BrandID:   system.Model.BrandID(),
   173  		Revision:  system.Model.Revision(),
   174  		Timestamp: system.Model.Timestamp(),
   175  		// SeedTime is intentionally left unset
   176  	}
   177  	return seededSys, nil
   178  }