diff --git a/cmd/control/install.go b/cmd/control/install.go index 954a7c6e..0fbdc9de 100755 --- a/cmd/control/install.go +++ b/cmd/control/install.go @@ -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 { diff --git a/cmd/control/install/install.go b/cmd/control/install/install.go index 0cb50e29..0005b9cc 100644 --- a/cmd/control/install/install.go +++ b/cmd/control/install/install.go @@ -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() +} diff --git a/cmd/control/install/syslinux.go b/cmd/control/install/syslinux.go index 9895a702..f19bc1fb 100644 --- a/cmd/control/install/syslinux.go +++ b/cmd/control/install/syslinux.go @@ -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 +} diff --git a/cmd/power/power.go b/cmd/power/power.go index b473394b..1a927c68 100644 --- a/cmd/power/power.go +++ b/cmd/power/power.go @@ -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) } diff --git a/cmd/power/shutdown.go b/cmd/power/shutdown.go index 2bccc7df..9620800e 100644 --- a/cmd/power/shutdown.go +++ b/cmd/power/shutdown.go @@ -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 } diff --git a/main.go b/main.go index 05f3378d..0e24e386 100644 --- a/main.go +++ b/main.go @@ -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() {