github.com/anonymouse64/snapd@v0.0.0-20210824153203-04c4c42d842d/overlord/devicestate/handlers.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  /*
     3   * Copyright (C) 2016-2020 Canonical Ltd
     4   *
     5   * This program is free software: you can redistribute it and/or modify
     6   * it under the terms of the GNU General Public License version 3 as
     7   * published by the Free Software Foundation.
     8   *
     9   * This program is distributed in the hope that it will be useful,
    10   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    11   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    12   * GNU General Public License for more details.
    13   *
    14   * You should have received a copy of the GNU General Public License
    15   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    16   *
    17   */
    18  
    19  package devicestate
    20  
    21  import (
    22  	"fmt"
    23  	"os/exec"
    24  	"time"
    25  
    26  	"gopkg.in/tomb.v2"
    27  
    28  	"github.com/snapcore/snapd/interfaces"
    29  	"github.com/snapcore/snapd/logger"
    30  	"github.com/snapcore/snapd/overlord/snapstate"
    31  	"github.com/snapcore/snapd/overlord/state"
    32  )
    33  
    34  func (m *DeviceManager) doMarkPreseeded(t *state.Task, _ *tomb.Tomb) error {
    35  	st := t.State()
    36  	st.Lock()
    37  	defer st.Unlock()
    38  
    39  	snaps, err := snapstate.All(st)
    40  	if err != nil {
    41  		return err
    42  	}
    43  
    44  	systemKey, err := interfaces.RecordedSystemKey()
    45  	if err != nil {
    46  		return fmt.Errorf("cannot get recorded system key: %v", err)
    47  	}
    48  
    49  	if m.preseed {
    50  		var preseeded bool
    51  		// the "preseeded" flag on this task is set to allow skipping the logic
    52  		// below in case this handler is retried in preseeding mode due to an
    53  		// EnsureBefore(0) done somewhere else.
    54  		// XXX: we should probably drop the flag from the task now that we have
    55  		// one on the state.
    56  		if err := t.Get("preseeded", &preseeded); err != nil && err != state.ErrNoState {
    57  			return err
    58  		}
    59  		if !preseeded {
    60  			preseeded = true
    61  			t.Set("preseeded", preseeded)
    62  			// unmount all snaps
    63  			// TODO: move to snapstate.UnmountAllSnaps.
    64  			for _, snapSt := range snaps {
    65  				info, err := snapSt.CurrentInfo()
    66  				if err != nil {
    67  					return err
    68  				}
    69  				logger.Debugf("unmounting snap %s at %s", info.InstanceName(), info.MountDir())
    70  				if _, err := exec.Command("umount", "-d", "-l", info.MountDir()).CombinedOutput(); err != nil {
    71  					return err
    72  				}
    73  			}
    74  
    75  			st.Set("preseeded", preseeded)
    76  			st.Set("preseed-system-key", systemKey)
    77  			st.Set("preseed-time", timeNow())
    78  
    79  			// do not mark this task done as this makes it racy against taskrunner tear down (the next task
    80  			// could start). Let this task finish after snapd restart when preseed mode is off.
    81  			st.RequestRestart(state.StopDaemon)
    82  		}
    83  
    84  		return &state.Retry{Reason: "mark-preseeded will be marked done when snapd is executed in normal mode"}
    85  	}
    86  
    87  	// normal snapd run after snapd restart (not in preseed mode anymore)
    88  
    89  	st.Set("seed-restart-system-key", systemKey)
    90  	if err := m.setTimeOnce("seed-restart-time", startTime); err != nil {
    91  		return err
    92  	}
    93  
    94  	return nil
    95  }
    96  
    97  type seededSystem struct {
    98  	// System carries the recovery system label that was used to seed the
    99  	// current system
   100  	System string `json:"system"`
   101  	Model  string `json:"model"`
   102  	// BrandID is the brand account ID
   103  	BrandID string `json:"brand-id"`
   104  	// Revision of the model assertion
   105  	Revision int `json:"revision"`
   106  	// Timestamp of model assertion
   107  	Timestamp time.Time `json:"timestamp"`
   108  	// SeedTime holds the timestamp when the system was seeded
   109  	SeedTime time.Time `json:"seed-time"`
   110  }
   111  
   112  func (s *seededSystem) sameAs(other *seededSystem) bool {
   113  	// in theory the system labels are unique, however be extra paranoid and
   114  	// check all model related fields too
   115  	return s.System == other.System &&
   116  		s.Model == other.Model &&
   117  		s.BrandID == other.BrandID &&
   118  		s.Revision == other.Revision
   119  }
   120  
   121  func (m *DeviceManager) recordSeededSystem(st *state.State, whatSeeded *seededSystem) error {
   122  	var seeded []seededSystem
   123  	if err := st.Get("seeded-systems", &seeded); err != nil && err != state.ErrNoState {
   124  		return err
   125  	}
   126  	for _, sys := range seeded {
   127  		if sys.sameAs(whatSeeded) {
   128  			return nil
   129  		}
   130  	}
   131  	// contrary to the usual approach of appending new entries to the list
   132  	// like we do with modeenv, the recently seeded system is added at the
   133  	// front, as it is not considered candidate like for the other entries,
   134  	// but rather it describes the currently existing
   135  	seeded = append([]seededSystem{*whatSeeded}, seeded...)
   136  	st.Set("seeded-systems", seeded)
   137  	return nil
   138  }
   139  
   140  func (m *DeviceManager) doMarkSeeded(t *state.Task, _ *tomb.Tomb) error {
   141  	st := t.State()
   142  	st.Lock()
   143  	defer st.Unlock()
   144  
   145  	if m.preseed {
   146  		return fmt.Errorf("internal error: mark-seeded task not expected in pre-seeding mode")
   147  	}
   148  
   149  	deviceCtx, err := DeviceCtx(st, t, nil)
   150  	if err != nil {
   151  		return fmt.Errorf("cannot get device context: %v", err)
   152  	}
   153  
   154  	if deviceCtx.HasModeenv() && deviceCtx.RunMode() {
   155  		modeEnv, err := maybeReadModeenv()
   156  		if err != nil {
   157  			return err
   158  		}
   159  		if modeEnv == nil {
   160  			return fmt.Errorf("missing modeenv, cannot proceed")
   161  		}
   162  		// unset recovery_system because that is only needed during install mode
   163  		modeEnv.RecoverySystem = ""
   164  		err = modeEnv.Write()
   165  		if err != nil {
   166  			return err
   167  		}
   168  	}
   169  
   170  	now := time.Now()
   171  	var whatSeeded *seededSystem
   172  	if err := t.Get("seed-system", &whatSeeded); err != nil && err != state.ErrNoState {
   173  		return err
   174  	}
   175  	if whatSeeded != nil && deviceCtx.RunMode() {
   176  		// record what seeded in the state only when in run mode
   177  		whatSeeded.SeedTime = now
   178  		if err := m.recordSeededSystem(st, whatSeeded); err != nil {
   179  			return fmt.Errorf("cannot record the seeded system: %v", err)
   180  		}
   181  	}
   182  	st.Set("seed-time", now)
   183  	st.Set("seeded", true)
   184  	// avoid possibly recording the same system multiple times etc.
   185  	t.SetStatus(state.DoneStatus)
   186  	// make sure we setup a fallback model/consider the next phase
   187  	// (registration) timely
   188  	st.EnsureBefore(0)
   189  	return nil
   190  }