gopkg.in/ubuntu-core/snappy.v0@v0.0.0-20210902073436-25a8614f10a6/overlord/devicestate/handlers_install.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2021 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  	"compress/gzip"
    24  	"fmt"
    25  	"os"
    26  	"os/exec"
    27  	"path/filepath"
    28  
    29  	"gopkg.in/tomb.v2"
    30  
    31  	"github.com/snapcore/snapd/asserts"
    32  	"github.com/snapcore/snapd/boot"
    33  	"github.com/snapcore/snapd/gadget"
    34  	"github.com/snapcore/snapd/gadget/install"
    35  	"github.com/snapcore/snapd/logger"
    36  	"github.com/snapcore/snapd/osutil"
    37  	"github.com/snapcore/snapd/overlord/snapstate"
    38  	"github.com/snapcore/snapd/overlord/state"
    39  	"github.com/snapcore/snapd/randutil"
    40  	"github.com/snapcore/snapd/secboot"
    41  	"github.com/snapcore/snapd/sysconfig"
    42  	"github.com/snapcore/snapd/timings"
    43  )
    44  
    45  var (
    46  	bootMakeRunnable            = boot.MakeRunnableSystem
    47  	bootEnsureNextBootToRunMode = boot.EnsureNextBootToRunMode
    48  	installRun                  = install.Run
    49  
    50  	sysconfigConfigureTargetSystem = sysconfig.ConfigureTargetSystem
    51  )
    52  
    53  func setSysconfigCloudOptions(opts *sysconfig.Options, gadgetDir string, model *asserts.Model) {
    54  	ubuntuSeedCloudCfg := filepath.Join(boot.InitramfsUbuntuSeedDir, "data/etc/cloud/cloud.cfg.d")
    55  
    56  	grade := model.Grade()
    57  
    58  	// we always set the cloud-init src directory if it exists, it is
    59  	// automatically ignored by sysconfig in the case it shouldn't be used
    60  	if osutil.IsDirectory(ubuntuSeedCloudCfg) {
    61  		opts.CloudInitSrcDir = ubuntuSeedCloudCfg
    62  	}
    63  
    64  	switch {
    65  	// if the gadget has a cloud.conf file, always use that regardless of grade
    66  	case sysconfig.HasGadgetCloudConf(gadgetDir):
    67  		opts.AllowCloudInit = true
    68  
    69  	// next thing is if are in secured grade and didn't have gadget config, we
    70  	// disable cloud-init always, clouds should have their own config via
    71  	// gadgets for grade secured
    72  	case grade == asserts.ModelSecured:
    73  		opts.AllowCloudInit = false
    74  
    75  	// all other cases we allow cloud-init to run, either through config that is
    76  	// available at runtime via a CI-DATA USB drive, or via config on
    77  	// ubuntu-seed if that is allowed by the model grade, etc.
    78  	default:
    79  		opts.AllowCloudInit = true
    80  	}
    81  }
    82  
    83  func writeModel(model *asserts.Model, where string) error {
    84  	f, err := os.OpenFile(where, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0644)
    85  	if err != nil {
    86  		return err
    87  	}
    88  	defer f.Close()
    89  	return asserts.NewEncoder(f).Encode(model)
    90  }
    91  
    92  func writeLogs(rootdir string) error {
    93  	// XXX: would be great to use native journal format but it's tied
    94  	//      to machine-id, we could journal -o export but there
    95  	//      is no systemd-journal-remote on core{,18,20}
    96  	//
    97  	// XXX: or only log if persistent journal is enabled?
    98  	logPath := filepath.Join(rootdir, "var/log/install-mode.log.gz")
    99  	if err := os.MkdirAll(filepath.Dir(logPath), 0755); err != nil {
   100  		return err
   101  	}
   102  
   103  	f, err := os.Create(logPath)
   104  	if err != nil {
   105  		return err
   106  	}
   107  	defer f.Close()
   108  
   109  	gz := gzip.NewWriter(f)
   110  	defer gz.Close()
   111  
   112  	cmd := exec.Command("journalctl", "-b", "0")
   113  	cmd.Stdout = gz
   114  	if err := cmd.Run(); err != nil {
   115  		return fmt.Errorf("cannot collect journal output: %v", err)
   116  	}
   117  	if err := gz.Flush(); err != nil {
   118  		return fmt.Errorf("cannot flush compressed log output: %v", err)
   119  	}
   120  
   121  	return nil
   122  }
   123  
   124  func writeTimings(st *state.State, rootdir string) error {
   125  	logPath := filepath.Join(rootdir, "var/log/install-timings.txt.gz")
   126  	if err := os.MkdirAll(filepath.Dir(logPath), 0755); err != nil {
   127  		return err
   128  	}
   129  
   130  	f, err := os.Create(logPath)
   131  	if err != nil {
   132  		return err
   133  	}
   134  	defer f.Close()
   135  
   136  	gz := gzip.NewWriter(f)
   137  	defer gz.Close()
   138  
   139  	var chgIDs []string
   140  	for _, chg := range st.Changes() {
   141  		if chg.Kind() == "seed" || chg.Kind() == "install-system" {
   142  			// this is captured via "--ensure=seed" and
   143  			// "--ensure=install-system" below
   144  			continue
   145  		}
   146  		chgIDs = append(chgIDs, chg.ID())
   147  	}
   148  
   149  	// state must be unlocked for "snap changes/debug timings" to work
   150  	st.Unlock()
   151  	defer st.Lock()
   152  
   153  	// XXX: ugly, ugly, but using the internal timings requires
   154  	//      some refactor as a lot of the required bits are not
   155  	//      exported right now
   156  	// first all changes
   157  	fmt.Fprintf(gz, "---- Output of: snap changes\n")
   158  	cmd := exec.Command("snap", "changes")
   159  	cmd.Stdout = gz
   160  	if err := cmd.Run(); err != nil {
   161  		return fmt.Errorf("cannot collect timings output: %v", err)
   162  	}
   163  	fmt.Fprintf(gz, "\n")
   164  	// then the seeding
   165  	fmt.Fprintf(gz, "---- Output of snap debug timings --ensure=seed\n")
   166  	cmd = exec.Command("snap", "debug", "timings", "--ensure=seed")
   167  	cmd.Stdout = gz
   168  	if err := cmd.Run(); err != nil {
   169  		return fmt.Errorf("cannot collect timings output: %v", err)
   170  	}
   171  	fmt.Fprintf(gz, "\n")
   172  	// then the install
   173  	fmt.Fprintf(gz, "---- Output of snap debug timings --ensure=install-system\n")
   174  	cmd = exec.Command("snap", "debug", "timings", "--ensure=install-system")
   175  	cmd.Stdout = gz
   176  	if err := cmd.Run(); err != nil {
   177  		return fmt.Errorf("cannot collect timings output: %v", err)
   178  	}
   179  	// then the other changes (if there are any)
   180  	for _, chgID := range chgIDs {
   181  		fmt.Fprintf(gz, "---- Output of snap debug timings %s\n", chgID)
   182  		cmd = exec.Command("snap", "debug", "timings", chgID)
   183  		cmd.Stdout = gz
   184  		if err := cmd.Run(); err != nil {
   185  			return fmt.Errorf("cannot collect timings output: %v", err)
   186  		}
   187  		fmt.Fprintf(gz, "\n")
   188  	}
   189  
   190  	if err := gz.Flush(); err != nil {
   191  		return fmt.Errorf("cannot flush timings output: %v", err)
   192  	}
   193  
   194  	return nil
   195  }
   196  
   197  func (m *DeviceManager) doSetupRunSystem(t *state.Task, _ *tomb.Tomb) error {
   198  	st := t.State()
   199  	st.Lock()
   200  	defer st.Unlock()
   201  
   202  	perfTimings := state.TimingsForTask(t)
   203  	defer perfTimings.Save(st)
   204  
   205  	// get gadget dir
   206  	deviceCtx, err := DeviceCtx(st, t, nil)
   207  	if err != nil {
   208  		return fmt.Errorf("cannot get device context: %v", err)
   209  	}
   210  	gadgetInfo, err := snapstate.GadgetInfo(st, deviceCtx)
   211  	if err != nil {
   212  		return fmt.Errorf("cannot get gadget info: %v", err)
   213  	}
   214  	gadgetDir := gadgetInfo.MountDir()
   215  
   216  	kernelInfo, err := snapstate.KernelInfo(st, deviceCtx)
   217  	if err != nil {
   218  		return fmt.Errorf("cannot get kernel info: %v", err)
   219  	}
   220  	kernelDir := kernelInfo.MountDir()
   221  
   222  	modeEnv, err := maybeReadModeenv()
   223  	if err != nil {
   224  		return err
   225  	}
   226  	if modeEnv == nil {
   227  		return fmt.Errorf("missing modeenv, cannot proceed")
   228  	}
   229  
   230  	// bootstrap
   231  	bopts := install.Options{
   232  		Mount: true,
   233  	}
   234  	useEncryption, err := m.checkEncryption(st, deviceCtx)
   235  	if err != nil {
   236  		return err
   237  	}
   238  	bopts.Encrypt = useEncryption
   239  
   240  	model := deviceCtx.Model()
   241  
   242  	// make sure that gadget is usable for the set up we want to use it in
   243  	validationConstraints := gadget.ValidationConstraints{
   244  		EncryptedData: useEncryption,
   245  	}
   246  	var ginfo *gadget.Info
   247  	timings.Run(perfTimings, "read-info-and-validate", "Read and validate gagdet info", func(timings.Measurer) {
   248  		ginfo, err = gadget.ReadInfoAndValidate(gadgetDir, model, &validationConstraints)
   249  	})
   250  	if err != nil {
   251  		return fmt.Errorf("cannot use gadget: %v", err)
   252  	}
   253  	if err := gadget.ValidateContent(ginfo, gadgetDir, kernelDir); err != nil {
   254  		return fmt.Errorf("cannot use gadget: %v", err)
   255  	}
   256  
   257  	var trustedInstallObserver *boot.TrustedAssetsInstallObserver
   258  	// get a nice nil interface by default
   259  	var installObserver gadget.ContentObserver
   260  	trustedInstallObserver, err = boot.TrustedAssetsInstallObserverForModel(model, gadgetDir, useEncryption)
   261  	if err != nil && err != boot.ErrObserverNotApplicable {
   262  		return fmt.Errorf("cannot setup asset install observer: %v", err)
   263  	}
   264  	if err == nil {
   265  		installObserver = trustedInstallObserver
   266  		if !useEncryption {
   267  			// there will be no key sealing, so past the
   268  			// installation pass no other methods need to be called
   269  			trustedInstallObserver = nil
   270  		}
   271  	}
   272  
   273  	var installedSystem *install.InstalledSystemSideData
   274  	// run the create partition code
   275  	logger.Noticef("create and deploy partitions")
   276  	timings.Run(perfTimings, "install-run", "Install the run system", func(tm timings.Measurer) {
   277  		st.Unlock()
   278  		defer st.Lock()
   279  		installedSystem, err = installRun(model, gadgetDir, kernelDir, "", bopts, installObserver, tm)
   280  	})
   281  	if err != nil {
   282  		return fmt.Errorf("cannot install system: %v", err)
   283  	}
   284  
   285  	if trustedInstallObserver != nil {
   286  		// sanity check
   287  		if installedSystem.KeysForRoles == nil || installedSystem.KeysForRoles[gadget.SystemData] == nil || installedSystem.KeysForRoles[gadget.SystemSave] == nil {
   288  			return fmt.Errorf("internal error: system encryption keys are unset")
   289  		}
   290  		dataKeySet := installedSystem.KeysForRoles[gadget.SystemData]
   291  		saveKeySet := installedSystem.KeysForRoles[gadget.SystemSave]
   292  
   293  		// make note of the encryption keys
   294  		trustedInstallObserver.ChosenEncryptionKeys(dataKeySet.Key, saveKeySet.Key)
   295  
   296  		// keep track of recovery assets
   297  		if err := trustedInstallObserver.ObserveExistingTrustedRecoveryAssets(boot.InitramfsUbuntuSeedDir); err != nil {
   298  			return fmt.Errorf("cannot observe existing trusted recovery assets: err")
   299  		}
   300  		if err := saveKeys(installedSystem.KeysForRoles); err != nil {
   301  			return err
   302  		}
   303  		// write markers containing a secret to pair data and save
   304  		if err := writeMarkers(); err != nil {
   305  			return err
   306  		}
   307  	}
   308  
   309  	// keep track of the model we installed
   310  	err = os.MkdirAll(filepath.Join(boot.InitramfsUbuntuBootDir, "device"), 0755)
   311  	if err != nil {
   312  		return fmt.Errorf("cannot store the model: %v", err)
   313  	}
   314  	err = writeModel(model, filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"))
   315  	if err != nil {
   316  		return fmt.Errorf("cannot store the model: %v", err)
   317  	}
   318  
   319  	// configure the run system
   320  	opts := &sysconfig.Options{TargetRootDir: boot.InstallHostWritableDir, GadgetDir: gadgetDir}
   321  	// configure cloud init
   322  	setSysconfigCloudOptions(opts, gadgetDir, model)
   323  	timings.Run(perfTimings, "sysconfig-configure-target-system", "Configure target system", func(timings.Measurer) {
   324  		err = sysconfigConfigureTargetSystem(model, opts)
   325  	})
   326  	if err != nil {
   327  		return err
   328  	}
   329  
   330  	// make it bootable
   331  	logger.Noticef("make system runnable")
   332  	bootBaseInfo, err := snapstate.BootBaseInfo(st, deviceCtx)
   333  	if err != nil {
   334  		return fmt.Errorf("cannot get boot base info: %v", err)
   335  	}
   336  	recoverySystemDir := filepath.Join("/systems", modeEnv.RecoverySystem)
   337  	bootWith := &boot.BootableSet{
   338  		Base:              bootBaseInfo,
   339  		BasePath:          bootBaseInfo.MountFile(),
   340  		Kernel:            kernelInfo,
   341  		KernelPath:        kernelInfo.MountFile(),
   342  		RecoverySystemDir: recoverySystemDir,
   343  		UnpackedGadgetDir: gadgetDir,
   344  	}
   345  	timings.Run(perfTimings, "boot-make-runnable", "Make target system runnable", func(timings.Measurer) {
   346  		err = bootMakeRunnable(deviceCtx.Model(), bootWith, trustedInstallObserver)
   347  	})
   348  	if err != nil {
   349  		return fmt.Errorf("cannot make system runnable: %v", err)
   350  	}
   351  
   352  	// TODO: FIXME: this should go away after we have time to design a proper
   353  	//              solution
   354  	// TODO: only run on specific models?
   355  
   356  	// on some specific devices, we need to create these directories in
   357  	// _writable_defaults in order to allow the install-device hook to install
   358  	// some files there, this eventually will go away when we introduce a proper
   359  	// mechanism not using system-files to install files onto the root
   360  	// filesystem from the install-device hook
   361  	if err := fixupWritableDefaultDirs(boot.InstallHostWritableDir); err != nil {
   362  		return err
   363  	}
   364  
   365  	return nil
   366  }
   367  
   368  func fixupWritableDefaultDirs(systemDataDir string) error {
   369  	// the _writable_default directory is used to put files in place on
   370  	// ubuntu-data from install mode, so we abuse it here for a specific device
   371  	// to let that device install files with system-files and the install-device
   372  	// hook
   373  
   374  	// eventually this will be a proper, supported, designed mechanism instead
   375  	// of just this hack, but this hack is just creating the directories, since
   376  	// the system-files interface only allows creating the file, not creating
   377  	// the directories leading up to that file, and since the file is deeply
   378  	// nested we would effectively have to give all permission to the device
   379  	// to create any file on ubuntu-data which we don't want to do, so we keep
   380  	// this restriction to let the device create one specific file, and then
   381  	// we behind the scenes just create the directories for the device
   382  
   383  	for _, subDirToCreate := range []string{"/etc/udev/rules.d", "/etc/modprobe.d", "/etc/modules-load.d/"} {
   384  		dirToCreate := sysconfig.WritableDefaultsDir(systemDataDir, subDirToCreate)
   385  
   386  		if err := os.MkdirAll(dirToCreate, 0755); err != nil {
   387  			return err
   388  		}
   389  	}
   390  
   391  	return nil
   392  }
   393  
   394  // writeMarkers writes markers containing the same secret to pair data and save.
   395  func writeMarkers() error {
   396  	// ensure directory for markers exists
   397  	if err := os.MkdirAll(boot.InstallHostFDEDataDir, 0755); err != nil {
   398  		return err
   399  	}
   400  	if err := os.MkdirAll(boot.InstallHostFDESaveDir, 0755); err != nil {
   401  		return err
   402  	}
   403  
   404  	// generate a secret random marker
   405  	markerSecret, err := randutil.CryptoTokenBytes(32)
   406  	if err != nil {
   407  		return fmt.Errorf("cannot create ubuntu-data/save marker secret: %v", err)
   408  	}
   409  
   410  	dataMarker := filepath.Join(boot.InstallHostFDEDataDir, "marker")
   411  	if err := osutil.AtomicWriteFile(dataMarker, markerSecret, 0600, 0); err != nil {
   412  		return err
   413  	}
   414  
   415  	saveMarker := filepath.Join(boot.InstallHostFDESaveDir, "marker")
   416  	if err := osutil.AtomicWriteFile(saveMarker, markerSecret, 0600, 0); err != nil {
   417  		return err
   418  	}
   419  
   420  	return nil
   421  }
   422  
   423  func saveKeys(keysForRoles map[string]*install.EncryptionKeySet) error {
   424  	dataKeySet := keysForRoles[gadget.SystemData]
   425  
   426  	// ensure directory for keys exists
   427  	if err := os.MkdirAll(boot.InstallHostFDEDataDir, 0755); err != nil {
   428  		return err
   429  	}
   430  
   431  	// Write the recovery key
   432  	recoveryKeyFile := filepath.Join(boot.InstallHostFDEDataDir, "recovery.key")
   433  	if err := dataKeySet.RecoveryKey.Save(recoveryKeyFile); err != nil {
   434  		return fmt.Errorf("cannot store recovery key: %v", err)
   435  	}
   436  
   437  	saveKeySet := keysForRoles[gadget.SystemSave]
   438  	if saveKeySet == nil {
   439  		// no system-save support
   440  		return nil
   441  	}
   442  
   443  	saveKey := filepath.Join(boot.InstallHostFDEDataDir, "ubuntu-save.key")
   444  	reinstallSaveKey := filepath.Join(boot.InstallHostFDEDataDir, "reinstall.key")
   445  
   446  	if err := saveKeySet.Key.Save(saveKey); err != nil {
   447  		return fmt.Errorf("cannot store system save key: %v", err)
   448  	}
   449  	if err := saveKeySet.RecoveryKey.Save(reinstallSaveKey); err != nil {
   450  		return fmt.Errorf("cannot store reinstall key: %v", err)
   451  	}
   452  	return nil
   453  }
   454  
   455  var secbootCheckTPMKeySealingSupported = secboot.CheckTPMKeySealingSupported
   456  
   457  // checkEncryption verifies whether encryption should be used based on the
   458  // model grade and the availability of a TPM device or a fde-setup hook
   459  // in the kernel.
   460  func (m *DeviceManager) checkEncryption(st *state.State, deviceCtx snapstate.DeviceContext) (res bool, err error) {
   461  	model := deviceCtx.Model()
   462  	secured := model.Grade() == asserts.ModelSecured
   463  	dangerous := model.Grade() == asserts.ModelDangerous
   464  	encrypted := model.StorageSafety() == asserts.StorageSafetyEncrypted
   465  
   466  	// check if we should disable encryption non-secured devices
   467  	// TODO:UC20: this is not the final mechanism to bypass encryption
   468  	if dangerous && osutil.FileExists(filepath.Join(boot.InitramfsUbuntuSeedDir, ".force-unencrypted")) {
   469  		return false, nil
   470  	}
   471  
   472  	// check if the model prefers to be unencrypted
   473  	// TODO: provide way to select via install chooser menu
   474  	//       if the install is unencrypted or encrypted
   475  	if model.StorageSafety() == asserts.StorageSafetyPreferUnencrypted {
   476  		logger.Noticef(`installing system unencrypted to comply with prefer-unencrypted storage-safety model option`)
   477  		return false, nil
   478  	}
   479  
   480  	// check if encryption is available
   481  	var (
   482  		hasFDESetupHook    bool
   483  		checkEncryptionErr error
   484  	)
   485  	if kernelInfo, err := snapstate.KernelInfo(st, deviceCtx); err == nil {
   486  		if hasFDESetupHook = hasFDESetupHookInKernel(kernelInfo); hasFDESetupHook {
   487  			checkEncryptionErr = m.checkFDEFeatures(st)
   488  		}
   489  	}
   490  	// Note that having a fde-setup hook will disable the build-in
   491  	// secboot encryption
   492  	if !hasFDESetupHook {
   493  		checkEncryptionErr = secbootCheckTPMKeySealingSupported()
   494  	}
   495  
   496  	// check if encryption is required
   497  	if checkEncryptionErr != nil {
   498  		if secured {
   499  			return false, fmt.Errorf("cannot encrypt device storage as mandated by model grade secured: %v", checkEncryptionErr)
   500  		}
   501  		if encrypted {
   502  			return false, fmt.Errorf("cannot encrypt device storage as mandated by encrypted storage-safety model option: %v", checkEncryptionErr)
   503  		}
   504  
   505  		if hasFDESetupHook {
   506  			logger.Noticef("not encrypting device storage as querying kernel fde-setup hook did not succeed: %v", checkEncryptionErr)
   507  		} else {
   508  			logger.Noticef("not encrypting device storage as checking TPM gave: %v", checkEncryptionErr)
   509  		}
   510  
   511  		// not required, go without
   512  		return false, nil
   513  	}
   514  
   515  	// encrypt
   516  	return true, nil
   517  }
   518  
   519  // RebootOptions can be attached to restart-system-to-run-mode tasks to control
   520  // their restart behavior.
   521  type RebootOptions struct {
   522  	Op string `json:"op,omitempty"`
   523  }
   524  
   525  const (
   526  	RebootHaltOp     = "halt"
   527  	RebootPoweroffOp = "poweroff"
   528  )
   529  
   530  func (m *DeviceManager) doRestartSystemToRunMode(t *state.Task, _ *tomb.Tomb) error {
   531  	st := t.State()
   532  	st.Lock()
   533  	defer st.Unlock()
   534  
   535  	perfTimings := state.TimingsForTask(t)
   536  	defer perfTimings.Save(st)
   537  
   538  	modeEnv, err := maybeReadModeenv()
   539  	if err != nil {
   540  		return err
   541  	}
   542  
   543  	if modeEnv == nil {
   544  		return fmt.Errorf("missing modeenv, cannot proceed")
   545  	}
   546  
   547  	// ensure the next boot goes into run mode
   548  	if err := bootEnsureNextBootToRunMode(modeEnv.RecoverySystem); err != nil {
   549  		return err
   550  	}
   551  
   552  	var rebootOpts RebootOptions
   553  	err = t.Get("reboot", &rebootOpts)
   554  	if err != nil && err != state.ErrNoState {
   555  		return err
   556  	}
   557  
   558  	// write timing information
   559  	if err := writeTimings(st, boot.InstallHostWritableDir); err != nil {
   560  		logger.Noticef("cannot write timings: %v", err)
   561  	}
   562  	// store install-mode log into ubuntu-data partition
   563  	if err := writeLogs(boot.InstallHostWritableDir); err != nil {
   564  		logger.Noticef("cannot write installation log: %v", err)
   565  	}
   566  
   567  	// request by default a restart as the last action after a
   568  	// successful install or what install-device requested via
   569  	// snapctl reboot
   570  	rst := state.RestartSystemNow
   571  	what := "restart"
   572  	switch rebootOpts.Op {
   573  	case RebootHaltOp:
   574  		what = "halt"
   575  		rst = state.RestartSystemHaltNow
   576  	case RebootPoweroffOp:
   577  		what = "poweroff"
   578  		rst = state.RestartSystemPoweroffNow
   579  	}
   580  	logger.Noticef("request immediate system %s", what)
   581  	st.RequestRestart(rst)
   582  
   583  	return nil
   584  }