1
0
mirror of https://github.com/rancher/os.git synced 2025-08-01 06:59:05 +00:00

reboot --kexec almost works

Signed-off-by: Sven Dowideit <SvenDowideit@home.org.au>
This commit is contained in:
Sven Dowideit 2017-07-05 14:14:22 +10:00
parent 5e4b5975a9
commit c5d4cb91c3
6 changed files with 356 additions and 204 deletions

View File

@ -152,7 +152,7 @@ func installAction(c *cli.Context) error {
if cloudConfig == "" {
if installType != "upgrade" {
// TODO: I wonder if its plausible to merge a new cloud-config into an existing one on upgrade - so for now, i'm only turning off the warning
log.Warn("Cloud-config not provided: you might need to provide cloud-config on bootDir with ssh_authorized_keys")
log.Warn("Cloud-config not provided: you might need to provide cloud-config on boot with ssh_authorized_keys")
}
} else {
os.MkdirAll("/opt", 0755)
@ -410,7 +410,6 @@ func layDownOS(image, installType, cloudConfig, device, partition, statedir, kap
//cloudConfig := SCRIPTS_DIR + "/conf/empty.yml" //${cloudConfig:-"${SCRIPTS_DIR}/conf/empty.yml"}
CONSOLE := "tty0"
baseName := "/mnt/new_img"
bootDir := "boot/"
kernelArgs := "printk.devkmsg=on rancher.state.dev=LABEL=RANCHER_STATE rancher.state.wait" // console="+CONSOLE
if statedir != "" {
kernelArgs = kernelArgs + " rancher.state.directory=" + statedir
@ -432,12 +431,12 @@ func layDownOS(image, installType, cloudConfig, device, partition, statedir, kap
case "generic":
log.Debugf("formatAndMount")
var err error
device, partition, err = formatAndMount(baseName, bootDir, device, partition)
device, partition, err = formatAndMount(baseName, device, partition)
if err != nil {
log.Errorf("formatAndMount %s", err)
return err
}
err = installSyslinux(device, baseName, bootDir, diskType)
err = installSyslinux(device, baseName, diskType)
if err != nil {
log.Errorf("installSyslinux %s", err)
return err
@ -449,7 +448,7 @@ func layDownOS(image, installType, cloudConfig, device, partition, statedir, kap
}
case "arm":
var err error
device, partition, err = formatAndMount(baseName, bootDir, device, partition)
device, partition, err = formatAndMount(baseName, device, partition)
if err != nil {
return err
}
@ -459,45 +458,45 @@ func layDownOS(image, installType, cloudConfig, device, partition, statedir, kap
case "amazon-ebs-hvm":
CONSOLE = "ttyS0"
var err error
device, partition, err = formatAndMount(baseName, bootDir, device, partition)
device, partition, err = formatAndMount(baseName, device, partition)
if err != nil {
return err
}
if installType == "amazon-ebs-hvm" {
installSyslinux(device, baseName, bootDir, diskType)
installSyslinux(device, baseName, diskType)
}
//# AWS Networking recommends disabling.
seedData(baseName, cloudConfig, FILES)
case "googlecompute":
CONSOLE = "ttyS0"
var err error
device, partition, err = formatAndMount(baseName, bootDir, device, partition)
device, partition, err = formatAndMount(baseName, device, partition)
if err != nil {
return err
}
installSyslinux(device, baseName, bootDir, diskType)
installSyslinux(device, baseName, diskType)
seedData(baseName, cloudConfig, FILES)
case "noformat":
var err error
device, partition, err = mountdevice(baseName, bootDir, device, partition, false)
device, partition, err = install.MountDevice(baseName, device, partition, false)
if err != nil {
return err
}
installSyslinux(device, baseName, bootDir, diskType)
installSyslinux(device, baseName, diskType)
if err := os.MkdirAll(filepath.Join(baseName, statedir), 0755); err != nil {
return err
}
case "raid":
var err error
device, partition, err = mountdevice(baseName, bootDir, device, partition, false)
device, partition, err = install.MountDevice(baseName, device, partition, false)
if err != nil {
return err
}
installSyslinux(device, baseName, bootDir, diskType)
installSyslinux(device, baseName, diskType)
case "bootstrap":
CONSOLE = "ttyS0"
var err error
device, partition, err = mountdevice(baseName, bootDir, device, partition, true)
device, partition, err = install.MountDevice(baseName, device, partition, true)
if err != nil {
return err
}
@ -507,39 +506,39 @@ func layDownOS(image, installType, cloudConfig, device, partition, statedir, kap
fallthrough
case "upgrade":
var err error
device, partition, err = mountdevice(baseName, bootDir, device, partition, false)
device, partition, err = install.MountDevice(baseName, device, partition, false)
if err != nil {
return err
}
log.Debugf("upgrading - %s, %s, %s, %s", device, baseName, bootDir, diskType)
log.Debugf("upgrading - %s, %s, %s, %s", device, baseName, diskType)
// TODO: detect pv-grub, and don't kill it with syslinux
upgradeBootloader(device, baseName, bootDir, diskType)
upgradeBootloader(device, baseName, diskType)
default:
return fmt.Errorf("unexpected install type %s", installType)
}
kernelArgs = kernelArgs + " console=" + CONSOLE
if kappend == "" {
preservedAppend, _ := ioutil.ReadFile(filepath.Join(baseName, bootDir+"append"))
preservedAppend, _ := ioutil.ReadFile(filepath.Join(baseName, install.BootDir+"append"))
kappend = string(preservedAppend)
} else {
ioutil.WriteFile(filepath.Join(baseName, bootDir+"append"), []byte(kappend), 0644)
ioutil.WriteFile(filepath.Join(baseName, install.BootDir+"append"), []byte(kappend), 0644)
}
if installType == "amazon-ebs-pv" {
menu := install.BootVars{
BaseName: baseName,
BootDir: bootDir,
BootDir: install.BootDir,
Timeout: 0,
Fallback: 0, // need to be conditional on there being a 'rollback'?
Entries: []install.MenuEntry{
install.MenuEntry{"RancherOS-current", bootDir, VERSION, kernelArgs, kappend},
install.MenuEntry{"RancherOS-current", install.BootDir, VERSION, kernelArgs, kappend},
},
}
install.PvGrubConfig(menu)
}
log.Debugf("installRancher")
currentCfg, err := installRancher(baseName, bootDir, VERSION, DIST, kernelArgs+" "+kappend)
currentCfg, err := installRancher(baseName, VERSION, DIST, kernelArgs+" "+kappend)
if err != nil {
log.Errorf("%s", err)
return err
@ -548,55 +547,12 @@ func layDownOS(image, installType, cloudConfig, device, partition, statedir, kap
// Used by upgrade
if kexec {
vmlinuzFile, initrdFile, err := readSyslinuxCfg(currentCfg)
if err != nil {
log.Errorf("%s", err)
return err
}
// kexec -l ${DIST}/vmlinuz --initrd=${DIST}/initrd --append="${kernelArgs} ${APPEND}" -f
cmd := exec.Command(
"kexec",
"-l", DIST+"/"+vmlinuzFile,
"--initrd", DIST+"/"+initrdFile,
"--append", "'"+kernelArgs+" "+kappend+"'",
"-f")
log.Debugf("Run(%#v)", cmd)
cmd.Stderr = os.Stderr
if _, err := cmd.Output(); err != nil {
log.Errorf("Failed to kexec: %s", err)
return err
}
log.Infof("kexec'd to new install")
power.Kexec(currentCfg, kernelArgs+" "+kappend)
}
return nil
}
func readSyslinuxCfg(currentCfg string) (string, string, error) {
vmlinuzFile := ""
initrdFile := ""
// Need to parse currentCfg for the lines:
// KERNEL ../vmlinuz-4.9.18-rancher^M
// INITRD ../initrd-41e02e6-dirty^M
buf, err := ioutil.ReadFile(currentCfg)
if err != nil {
return vmlinuzFile, initrdFile, err
}
s := bufio.NewScanner(bytes.NewReader(buf))
for s.Scan() {
line := strings.TrimSpace(s.Text())
if strings.HasPrefix(line, "KERNEL") {
vmlinuzFile = strings.TrimSpace(strings.TrimPrefix(line, "KERNEL"))
vmlinuzFile = filepath.Base(vmlinuzFile)
}
if strings.HasPrefix(line, "INITRD") {
initrdFile = strings.TrimSpace(strings.TrimPrefix(line, "INITRD"))
initrdFile = filepath.Base(initrdFile)
}
}
return vmlinuzFile, initrdFile, err
}
// files is an array of 'sourcefile:destination' - but i've not seen any examples of it being used.
func seedData(baseName, cloudData string, files []string) error {
log.Debugf("seedData")
@ -772,59 +728,7 @@ func formatdevice(device, partition string) error {
return nil
}
func mountdevice(baseName, bootDir, device, partition string, raw bool) (string, string, error) {
log.Debugf("mountdevice %s, raw %v", partition, raw)
if partition == "" {
if raw {
log.Debugf("util.Mount (raw) %s, %s", partition, baseName)
cmd := exec.Command("lsblk", "-no", "pkname", partition)
log.Debugf("Run(%v)", cmd)
cmd.Stderr = os.Stderr
device := ""
// TODO: out can == "" - this is used to "detect software RAID" which is terrible
if out, err := cmd.Output(); err == nil {
device = "/dev/" + strings.TrimSpace(string(out))
}
log.Debugf("mountdevice return -> d: %s, p: %s", device, partition)
return device, partition, util.Mount(partition, baseName, "", "")
}
//rootfs := partition
// Don't use ResolveDevice - it can fail, whereas `blkid -L LABEL` works more often
cfg := config.LoadConfig()
if d, _ := util.Blkid("RANCHER_BOOT"); d != "" {
partition = d
baseName = filepath.Join(baseName, "boot")
} else {
if dev := util.ResolveDevice(cfg.Rancher.State.Dev); dev != "" {
// try the rancher.state.dev setting
partition = dev
} else {
if d, _ := util.Blkid("RANCHER_STATE"); d != "" {
partition = d
}
}
}
cmd := exec.Command("lsblk", "-no", "pkname", partition)
log.Debugf("Run(%v)", cmd)
cmd.Stderr = os.Stderr
// TODO: out can == "" - this is used to "detect software RAID" which is terrible
if out, err := cmd.Output(); err == nil {
device = "/dev/" + strings.TrimSpace(string(out))
}
}
os.MkdirAll(baseName, 0755)
cmd := exec.Command("mount", partition, baseName)
//cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
log.Debugf("mountdevice return2 -> d: %s, p: %s", device, partition)
return device, partition, cmd.Run()
}
func formatAndMount(baseName, bootDir, device, partition string) (string, string, error) {
func formatAndMount(baseName, device, partition string) (string, string, error) {
log.Debugf("formatAndMount")
err := formatdevice(device, partition)
@ -832,31 +736,14 @@ func formatAndMount(baseName, bootDir, device, partition string) (string, string
log.Errorf("formatdevice %s", err)
return device, partition, err
}
device, partition, err = mountdevice(baseName, bootDir, device, partition, false)
device, partition, err = install.MountDevice(baseName, device, partition, false)
if err != nil {
log.Errorf("mountdevice %s", err)
return device, partition, err
}
//err = createbootDirs(baseName, bootDir)
//if err != nil {
// log.Errorf("createbootDirs %s", err)
// return bootDir, err
//}
return device, partition, nil
}
func NOPEcreatebootDir(baseName, bootDir string) error {
log.Debugf("createbootDirs")
if err := os.MkdirAll(filepath.Join(baseName, bootDir+"grub"), 0755); err != nil {
return err
}
if err := os.MkdirAll(filepath.Join(baseName, bootDir+"syslinux"), 0755); err != nil {
return err
}
return nil
}
func setBootable(device, diskType string) error {
// TODO make conditional - if there is a bootable device already, don't break it
// TODO: make RANCHER_BOOT bootable - it might not be device 1
@ -875,10 +762,10 @@ func setBootable(device, diskType string) error {
return nil
}
func upgradeBootloader(device, baseName, bootDir, diskType string) error {
func upgradeBootloader(device, baseName, diskType string) error {
log.Debugf("start upgradeBootloader")
grubDir := filepath.Join(baseName, bootDir+"grub")
grubDir := filepath.Join(baseName, install.BootDir+"grub")
if _, err := os.Stat(grubDir); os.IsNotExist(err) {
log.Debugf("%s does not exist - no need to upgrade bootloader", grubDir)
// we've already upgraded
@ -886,12 +773,12 @@ func upgradeBootloader(device, baseName, bootDir, diskType string) error {
return nil
}
// deal with systems which were previously upgraded, then rolled back, and are now being re-upgraded
grubBackup := filepath.Join(baseName, bootDir+"grub_backup")
grubBackup := filepath.Join(baseName, install.BootDir+"grub_backup")
if err := os.RemoveAll(grubBackup); err != nil {
log.Errorf("RemoveAll (%s): %s", grubBackup, err)
return err
}
backupSyslinuxDir := filepath.Join(baseName, bootDir+"syslinux_backup")
backupSyslinuxDir := filepath.Join(baseName, install.BootDir+"syslinux_backup")
if _, err := os.Stat(backupSyslinuxDir); !os.IsNotExist(err) {
backupSyslinuxLdlinuxSys := filepath.Join(backupSyslinuxDir, "ldlinux.sys")
if _, err := os.Stat(backupSyslinuxLdlinuxSys); !os.IsNotExist(err) {
@ -914,7 +801,7 @@ func upgradeBootloader(device, baseName, bootDir, diskType string) error {
return err
}
syslinuxDir := filepath.Join(baseName, bootDir+"syslinux")
syslinuxDir := filepath.Join(baseName, install.BootDir+"syslinux")
// it seems that v0.5.0 didn't have a syslinux dir, while 0.7 does
if _, err := os.Stat(syslinuxDir); !os.IsNotExist(err) {
if err := os.Rename(syslinuxDir, backupSyslinuxDir); err != nil {
@ -935,15 +822,15 @@ func upgradeBootloader(device, baseName, bootDir, diskType string) error {
cfg = strings.Replace(cfg, "current", "previous", -1)
// TODO consider removing the APPEND line - as the global.cfg should have the same result
ioutil.WriteFile(filepath.Join(baseName, bootDir, "linux-current.cfg"), []byte(cfg), 0644)
ioutil.WriteFile(filepath.Join(baseName, install.BootDir, "linux-current.cfg"), []byte(cfg), 0644)
lines := strings.Split(cfg, "\n")
for _, line := range lines {
line = strings.TrimSpace(line)
if strings.HasPrefix(line, "APPEND") {
log.Errorf("write new (%s) %s", filepath.Join(baseName, bootDir, "global.cfg"), err)
log.Errorf("write new (%s) %s", filepath.Join(baseName, install.BootDir, "global.cfg"), err)
// TODO: need to append any extra's the user specified
ioutil.WriteFile(filepath.Join(baseName, bootDir, "global.cfg"), []byte(cfg), 0644)
ioutil.WriteFile(filepath.Join(baseName, install.BootDir, "global.cfg"), []byte(cfg), 0644)
break
}
}
@ -951,10 +838,10 @@ func upgradeBootloader(device, baseName, bootDir, diskType string) error {
}
}
return installSyslinux(device, baseName, bootDir, diskType)
return installSyslinux(device, baseName, diskType)
}
func installSyslinux(device, baseName, bootDir, diskType string) error {
func installSyslinux(device, baseName, diskType string) error {
log.Debugf("installSyslinux(%s)", device)
mbrFile := "mbr.bin"
@ -1004,7 +891,7 @@ func installSyslinux(device, baseName, bootDir, diskType string) error {
}
}
sysLinuxDir := filepath.Join(baseName, bootDir, "syslinux")
sysLinuxDir := filepath.Join(baseName, install.BootDir, "syslinux")
if err := os.MkdirAll(sysLinuxDir, 0755); err != nil {
log.Errorf("MkdirAll(%s)): %s", sysLinuxDir, err)
//return err
@ -1037,13 +924,13 @@ func installSyslinux(device, baseName, bootDir, diskType string) error {
return nil
}
func installRancher(baseName, bootDir, VERSION, DIST, kappend string) (string, error) {
func installRancher(baseName, VERSION, DIST, kappend string) (string, error) {
log.Debugf("installRancher")
// detect if there already is a linux-current.cfg, if so, move it to linux-previous.cfg,
currentCfg := filepath.Join(baseName, bootDir, "linux-current.cfg")
currentCfg := filepath.Join(baseName, install.BootDir, "linux-current.cfg")
if _, err := os.Stat(currentCfg); !os.IsNotExist(err) {
previousCfg := filepath.Join(baseName, bootDir, "linux-previous.cfg")
previousCfg := filepath.Join(baseName, install.BootDir, "linux-previous.cfg")
if _, err := os.Stat(previousCfg); !os.IsNotExist(err) {
if err := os.Remove(previousCfg); err != nil {
return currentCfg, err
@ -1059,19 +946,19 @@ func installRancher(baseName, bootDir, VERSION, DIST, kappend string) (string, e
if file.IsDir() {
continue
}
if err := dfs.CopyFile(filepath.Join(DIST, file.Name()), filepath.Join(baseName, bootDir), file.Name()); err != nil {
if err := dfs.CopyFile(filepath.Join(DIST, file.Name()), filepath.Join(baseName, install.BootDir), file.Name()); err != nil {
log.Errorf("copy %s: %s", file.Name(), err)
//return err
}
}
// the general INCLUDE syslinuxcfg
if err := dfs.CopyFile(filepath.Join(DIST, "isolinux", "isolinux.cfg"), filepath.Join(baseName, bootDir, "syslinux"), "syslinux.cfg"); err != nil {
if err := dfs.CopyFile(filepath.Join(DIST, "isolinux", "isolinux.cfg"), filepath.Join(baseName, install.BootDir, "syslinux"), "syslinux.cfg"); err != nil {
log.Errorf("copy global syslinux.cfgS%s: %s", "syslinux.cfg", err)
//return err
}
// The global.cfg INCLUDE - useful for over-riding the APPEND line
globalFile := filepath.Join(filepath.Join(baseName, bootDir), "global.cfg")
globalFile := filepath.Join(filepath.Join(baseName, install.BootDir), "global.cfg")
if _, err := os.Stat(globalFile); !os.IsNotExist(err) {
err := ioutil.WriteFile(globalFile, []byte("APPEND "+kappend), 0644)
if err != nil {

View File

@ -1,5 +1,18 @@
package install
import (
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/rancher/os/config"
"github.com/rancher/os/log"
"github.com/rancher/os/util"
)
const BootDir = "boot/"
type MenuEntry struct {
Name, BootDir, Version, KernelArgs, Append string
}
@ -9,3 +22,55 @@ type BootVars struct {
Fallback int
Entries []MenuEntry
}
func MountDevice(baseName, device, partition string, raw bool) (string, string, error) {
log.Debugf("mountdevice %s, raw %v", partition, raw)
if partition == "" {
if raw {
log.Debugf("util.Mount (raw) %s, %s", partition, baseName)
cmd := exec.Command("lsblk", "-no", "pkname", partition)
log.Debugf("Run(%v)", cmd)
cmd.Stderr = os.Stderr
device := ""
// TODO: out can == "" - this is used to "detect software RAID" which is terrible
if out, err := cmd.Output(); err == nil {
device = "/dev/" + strings.TrimSpace(string(out))
}
log.Debugf("mountdevice return -> d: %s, p: %s", device, partition)
return device, partition, util.Mount(partition, baseName, "", "")
}
//rootfs := partition
// Don't use ResolveDevice - it can fail, whereas `blkid -L LABEL` works more often
cfg := config.LoadConfig()
if d, _ := util.Blkid("RANCHER_BOOT"); d != "" {
partition = d
baseName = filepath.Join(baseName, BootDir)
} else {
if dev := util.ResolveDevice(cfg.Rancher.State.Dev); dev != "" {
// try the rancher.state.dev setting
partition = dev
} else {
if d, _ := util.Blkid("RANCHER_STATE"); d != "" {
partition = d
}
}
}
cmd := exec.Command("lsblk", "-no", "pkname", partition)
log.Debugf("Run(%v)", cmd)
cmd.Stderr = os.Stderr
// TODO: out can == "" - this is used to "detect software RAID" which is terrible
if out, err := cmd.Output(); err == nil {
device = "/dev/" + strings.TrimSpace(string(out))
}
}
os.MkdirAll(baseName, 0755)
cmd := exec.Command("mount", partition, baseName)
//cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
log.Debugf("mountdevice return2 -> d: %s, p: %s", device, partition)
return device, partition, cmd.Run()
}

View File

@ -1,9 +1,13 @@
package install
import (
"bufio"
"bytes"
"html/template"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/rancher/os/log"
)
@ -43,3 +47,48 @@ DEFAULT RancherOS-current
}
return nil
}
func ReadGlobalCfg(globalCfg string) (string, error) {
append := ""
buf, err := ioutil.ReadFile(globalCfg)
if err != nil {
return append, err
}
s := bufio.NewScanner(bytes.NewReader(buf))
for s.Scan() {
line := strings.TrimSpace(s.Text())
if strings.HasPrefix(line, "APPEND") {
append = strings.TrimSpace(strings.TrimPrefix(line, "APPEND"))
}
}
return append, nil
}
func ReadSyslinuxCfg(currentCfg string) (string, string, error) {
vmlinuzFile := ""
initrdFile := ""
// Need to parse currentCfg for the lines:
// KERNEL ../vmlinuz-4.9.18-rancher^M
// INITRD ../initrd-41e02e6-dirty^M
buf, err := ioutil.ReadFile(currentCfg)
if err != nil {
return vmlinuzFile, initrdFile, err
}
DIST := filepath.Dir(currentCfg)
s := bufio.NewScanner(bytes.NewReader(buf))
for s.Scan() {
line := strings.TrimSpace(s.Text())
if strings.HasPrefix(line, "KERNEL") {
vmlinuzFile = strings.TrimSpace(strings.TrimPrefix(line, "KERNEL"))
vmlinuzFile = filepath.Join(DIST, filepath.Base(vmlinuzFile))
}
if strings.HasPrefix(line, "INITRD") {
initrdFile = strings.TrimSpace(strings.TrimPrefix(line, "INITRD"))
initrdFile = filepath.Join(DIST, filepath.Base(initrdFile))
}
}
return vmlinuzFile, initrdFile, err
}

View File

@ -19,6 +19,9 @@ import (
"github.com/rancher/os/util"
)
// You can't shutdown the system from a process in console because we want to stop the console container.
// If you do that you kill yourself. So we spawn a separate container to do power operations
// This can up because on shutdown we want ssh to gracefully die, terminating ssh connections and not just hanging tcp session
func runDocker(name string) error {
if os.ExpandEnv("${IN_DOCKER}") == "true" {
return nil
@ -100,40 +103,24 @@ func runDocker(name string) error {
return nil
}
func common(name string) {
func reboot(name string, force bool, code uint) {
if os.Geteuid() != 0 {
log.Fatalf("%s: Need to be root", os.Args[0])
}
if err := runDocker(name); err != nil {
log.Fatal(err)
}
}
func Off() {
common("poweroff")
reboot(syscall.LINUX_REBOOT_CMD_POWER_OFF)
}
func Reboot() {
common("reboot")
reboot(syscall.LINUX_REBOOT_CMD_RESTART)
}
func Halt() {
common("halt")
reboot(syscall.LINUX_REBOOT_CMD_HALT)
}
func reboot(code uint) {
err := shutDownContainers()
if err != nil {
log.Error(err)
if !force {
if err := runDocker(name); err != nil {
log.Fatal(err)
}
err := shutDownContainers()
if err != nil {
log.Error(err)
}
}
syscall.Sync()
err = syscall.Reboot(int(code))
err := syscall.Reboot(int(code))
if err != nil {
log.Fatal(err)
}

View File

@ -1,14 +1,28 @@
package power
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"syscall"
"github.com/codegangsta/cli"
"github.com/rancher/os/cmd/control/install"
"github.com/rancher/os/config"
"github.com/rancher/os/log"
"github.com/rancher/os/util"
)
func Main() {
var (
haltFlag bool
poweroffFlag bool
rebootFlag bool
forceFlag bool
kexecFlag bool
)
func Shutdown() {
log.InitLogger()
app := cli.NewApp()
@ -16,33 +30,181 @@ func Main() {
app.Usage = "Control and configure RancherOS"
app.Version = config.Version
app.Author = "Rancher Labs, Inc."
app.Email = "sid@rancher.com"
app.EnableBashCompletion = true
app.Action = shutdown
app.Flags = []cli.Flag{
cli.StringFlag{
Name: "r, R",
Usage: "reboot after shutdown",
},
cli.StringFlag{
Name: "h",
Usage: "halt the system",
// --no-wall
// Do not send wall message before halt, power-off,
// reboot.
// halt, poweroff, reboot ONLY
// -f, --force
// Force immediate halt, power-off, reboot. Do not
// contact the init system.
cli.BoolFlag{
Name: "f, force",
Usage: "Force immediate halt, power-off, reboot. Do not contact the init system.",
Destination: &forceFlag,
},
// -w, --wtmp-only
// Only write wtmp shutdown entry, do not actually
// halt, power-off, reboot.
// -d, --no-wtmp
// Do not write wtmp shutdown entry.
// -n, --no-sync
// Don't sync hard disks/storage media before halt,
// power-off, reboot.
// shutdown ONLY
// -h
// Equivalent to --poweroff, unless --halt is
// specified.
// -k
// Do not halt, power-off, reboot, just write wall
// message.
// -c
// Cancel a pending shutdown. This may be used
// cancel the effect of an invocation of shutdown
// with a time argument that is not "+0" or "now".
}
app.HideHelp = true
// -H, --halt
// Halt the machine.
if app.Name == "halt" {
app.Flags = append(app.Flags, cli.BoolTFlag{
Name: "H, halt",
Usage: "halt the machine",
Destination: &haltFlag,
})
} else {
app.Flags = append(app.Flags, cli.BoolFlag{
Name: "H, halt",
Usage: "halt the machine",
Destination: &haltFlag,
})
}
// -P, --poweroff
// Power-off the machine (the default for shutdown cmd).
if app.Name == "poweroff" {
app.Flags = append(app.Flags, cli.BoolTFlag{
Name: "P, poweroff",
Usage: "halt the machine",
Destination: &poweroffFlag,
})
} else {
app.Flags = append(app.Flags, cli.BoolFlag{
Name: "P, poweroff",
Usage: "halt the machine",
Destination: &poweroffFlag,
})
}
// -r, --reboot
// Reboot the machine.
if app.Name == "reboot" {
app.Flags = append(app.Flags, cli.BoolTFlag{
Name: "r, reboot",
Usage: "reboot after shutdown",
Destination: &rebootFlag,
})
// OR? maybe implement it as a `kexec` cli tool?
app.Flags = append(app.Flags, cli.BoolFlag{
Name: "kexec",
Usage: "kexec the default kernel",
Destination: &kexecFlag,
})
} else {
app.Flags = append(app.Flags, cli.BoolFlag{
Name: "r, reboot",
Usage: "reboot after shutdown",
Destination: &rebootFlag,
})
}
//TODO: add the time and msg flags...
app.Run(os.Args)
}
func shutdown(c *cli.Context) error {
common("")
reboot := c.String("r")
poweroff := c.String("h")
if reboot == "now" {
Reboot()
} else if poweroff == "now" {
Off()
func Kexec(bootDir, append string) error {
cfgFile := filepath.Join(bootDir, "linux-current.cfg")
vmlinuzFile, initrdFile, err := install.ReadSyslinuxCfg(cfgFile)
if err != nil {
log.Errorf("%s", err)
return err
}
globalCfgFile := filepath.Join(bootDir, "global.cfg")
if append == "" {
append, err = install.ReadGlobalCfg(globalCfgFile)
if err != nil {
log.Errorf("%s", err)
return err
}
}
// TODO: read global.cfg if append == ""
// kexec -l ${DIST}/vmlinuz --initrd=${DIST}/initrd --append="${kernelArgs} ${APPEND}" -f
cmd := exec.Command(
"kexec",
"-l", vmlinuzFile,
"--initrd", initrdFile,
"--append", "'"+append+"'",
"-f")
log.Debugf("Run(%#v)", cmd)
cmd.Stderr = os.Stderr
if _, err := cmd.Output(); err != nil {
log.Errorf("Failed to kexec: %s", err)
return err
}
log.Infof("kexec'd to new install")
return nil
}
// Reboot is used by installation / upgrade
// TODO: add kexec option
func Reboot() {
reboot("reboot", false, syscall.LINUX_REBOOT_CMD_RESTART)
}
func shutdown(c *cli.Context) error {
if kexecFlag {
if os.Geteuid() != 0 {
log.Fatalf("%s: Need to be root", os.Args[0])
}
// need to mount boot dir, or `system-docker run -v /:/host -w /host/boot` ?
baseName := "/mnt/new_img"
_, _, err := install.MountDevice(baseName, "", "", false)
if err != nil {
log.Errorf("ERROR: can't Kexec: %s", err)
return nil
}
defer util.Unmount(baseName)
return Kexec(filepath.Join(baseName, install.BootDir), "")
}
// the shutdown command's default is poweroff
var powerCmd uint
powerCmd = syscall.LINUX_REBOOT_CMD_POWER_OFF
if rebootFlag {
powerCmd = syscall.LINUX_REBOOT_CMD_RESTART
} else if poweroffFlag {
powerCmd = syscall.LINUX_REBOOT_CMD_POWER_OFF
} else if haltFlag {
powerCmd = syscall.LINUX_REBOOT_CMD_HALT
}
timeArg := c.Args().Get(0)
if c.App.Name == "shutdown" && timeArg != "" {
if timeArg != "now" {
err := fmt.Errorf("Sorry, can't parse '%s' as time value (only 'now' supported)", timeArg)
log.Error(err)
return err
}
// TODO: if there are more params, LOG them
}
reboot(c.App.Name, forceFlag, powerCmd)
return nil
}

12
main.go
View File

@ -29,19 +29,21 @@ var entrypoints = map[string]func(){
"console.sh": control.ConsoleInitMain,
"docker": docker.Main,
"dockerlaunch": dfs.Main,
"halt": power.Halt,
"init": osInit.MainInit,
"netconf": network.Main,
"poweroff": power.Off,
"reboot": power.Reboot,
"respawn": respawn.Main,
"ros-sysinit": sysinit.Main,
"shutdown": power.Main,
"system-docker": systemdocker.Main,
"wait-for-docker": wait.Main,
"cni-glue": glue.Main,
"bridge": bridge.Main,
"host-local": hostlocal.Main,
"respawn": respawn.Main,
// Power commands
"halt": power.Shutdown,
"poweroff": power.Shutdown,
"reboot": power.Shutdown,
"shutdown": power.Shutdown,
}
func main() {