github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/overlord/hookstate/ctlcmd/reboot.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 ctlcmd
    21  
    22  import (
    23  	"fmt"
    24  
    25  	"github.com/snapcore/snapd/i18n"
    26  	"github.com/snapcore/snapd/overlord/devicestate"
    27  )
    28  
    29  var (
    30  	shortRebootHelp = i18n.G("Control the reboot behavior of the system")
    31  	longRebootHelp  = i18n.G(`
    32  The reboot command can used from allowed hooks to control the reboot behavior of the system.
    33  
    34  Currently it can only be invoked from gadget install-device during UC20 install mode. After invoking it from install-device with --halt or --poweroff the device will not reboot into run mode after finishing install mode but will instead either halt or power off. From install-device the effect is therefore not immediate but delayed until the end of installation itself.
    35  `)
    36  )
    37  
    38  func init() {
    39  	addCommand("reboot", shortRebootHelp, longRebootHelp, func() command { return &rebootCommand{} })
    40  }
    41  
    42  type rebootCommand struct {
    43  	baseCommand
    44  
    45  	Halt     bool `long:"halt"`
    46  	Poweroff bool `long:"poweroff"`
    47  }
    48  
    49  func (c *rebootCommand) Execute([]string) error {
    50  	ctx := c.context()
    51  	if ctx == nil {
    52  		return fmt.Errorf(i18n.G("cannot use reboot command without a context"))
    53  	}
    54  	if ctx.HookName() != "install-device" {
    55  		return fmt.Errorf("cannot use reboot command outside of gadget install-device hook")
    56  	}
    57  	task, ok := ctx.Task()
    58  	if !ok {
    59  		return fmt.Errorf("internal error: inside gadget install-device hook but no task")
    60  	}
    61  	if !c.Halt && !c.Poweroff {
    62  		return fmt.Errorf("either --halt or --poweroff must be specified")
    63  	}
    64  	if c.Halt && c.Poweroff {
    65  		return fmt.Errorf("cannot specify both --halt and --poweroff")
    66  	}
    67  
    68  	ctx.Lock()
    69  	defer ctx.Unlock()
    70  	st := ctx.State()
    71  
    72  	var restartTaskID string
    73  	err := task.Get("restart-task", &restartTaskID)
    74  	if err != nil {
    75  		return fmt.Errorf("internal error: cannot get restart-task following install-device hook: %v", err)
    76  	}
    77  	restartTask := st.Task(restartTaskID)
    78  	if restartTask == nil {
    79  		// same error as TaskSnapSetup
    80  		return fmt.Errorf("internal error: tasks are being pruned")
    81  	}
    82  
    83  	var op string
    84  	if c.Halt {
    85  		op = devicestate.RebootHaltOp
    86  	} else if c.Poweroff {
    87  		op = devicestate.RebootPoweroffOp
    88  	}
    89  
    90  	restartTask.Set("reboot", devicestate.RebootOptions{
    91  		Op: op,
    92  	})
    93  
    94  	return nil
    95  }