shutdown.go 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. package power
  2. import (
  3. "fmt"
  4. "os"
  5. "os/exec"
  6. "path/filepath"
  7. "syscall"
  8. "github.com/rancher/os/cmd/control/install"
  9. "github.com/rancher/os/config"
  10. "github.com/rancher/os/pkg/log"
  11. "github.com/codegangsta/cli"
  12. )
  13. var (
  14. haltFlag bool
  15. poweroffFlag bool
  16. rebootFlag bool
  17. forceFlag bool
  18. kexecFlag bool
  19. previouskexecFlag bool
  20. kexecAppendFlag string
  21. )
  22. func Shutdown() {
  23. log.InitLogger()
  24. app := cli.NewApp()
  25. app.Name = filepath.Base(os.Args[0])
  26. app.Usage = fmt.Sprintf("%s RancherOS\nbuilt: %s", app.Name, config.BuildDate)
  27. app.Version = config.Version
  28. app.Author = "Rancher Labs, Inc."
  29. app.EnableBashCompletion = true
  30. app.Action = shutdown
  31. app.Flags = []cli.Flag{
  32. // --no-wall
  33. // Do not send wall message before halt, power-off,
  34. // reboot.
  35. // halt, poweroff, reboot ONLY
  36. // -f, --force
  37. // Force immediate halt, power-off, reboot. Do not
  38. // contact the init system.
  39. cli.BoolFlag{
  40. Name: "f, force",
  41. Usage: "Force immediate halt, power-off, reboot. Do not contact the init system.",
  42. Destination: &forceFlag,
  43. },
  44. // -w, --wtmp-only
  45. // Only write wtmp shutdown entry, do not actually
  46. // halt, power-off, reboot.
  47. // -d, --no-wtmp
  48. // Do not write wtmp shutdown entry.
  49. // -n, --no-sync
  50. // Don't sync hard disks/storage media before halt,
  51. // power-off, reboot.
  52. // shutdown ONLY
  53. // -h
  54. // Equivalent to --poweroff, unless --halt is
  55. // specified.
  56. // -k
  57. // Do not halt, power-off, reboot, just write wall
  58. // message.
  59. // -c
  60. // Cancel a pending shutdown. This may be used
  61. // cancel the effect of an invocation of shutdown
  62. // with a time argument that is not "+0" or "now".
  63. }
  64. // -H, --halt
  65. // Halt the machine.
  66. if app.Name == "halt" {
  67. app.Flags = append(app.Flags, cli.BoolTFlag{
  68. Name: "H, halt",
  69. Usage: "halt the machine",
  70. Destination: &haltFlag,
  71. })
  72. } else {
  73. app.Flags = append(app.Flags, cli.BoolFlag{
  74. Name: "H, halt",
  75. Usage: "halt the machine",
  76. Destination: &haltFlag,
  77. })
  78. }
  79. // -P, --poweroff
  80. // Power-off the machine (the default for shutdown cmd).
  81. if app.Name == "poweroff" {
  82. app.Flags = append(app.Flags, cli.BoolTFlag{
  83. Name: "P, poweroff",
  84. Usage: "poweroff the machine",
  85. Destination: &poweroffFlag,
  86. })
  87. } else {
  88. // shutdown -h
  89. // Equivalent to --poweroff
  90. if app.Name == "shutdown" {
  91. app.Flags = append(app.Flags, cli.BoolFlag{
  92. Name: "h",
  93. Usage: "poweroff the machine",
  94. Destination: &poweroffFlag,
  95. })
  96. }
  97. app.Flags = append(app.Flags, cli.BoolFlag{
  98. Name: "P, poweroff",
  99. Usage: "poweroff the machine",
  100. Destination: &poweroffFlag,
  101. })
  102. }
  103. // -r, --reboot
  104. // Reboot the machine.
  105. if app.Name == "reboot" {
  106. app.Flags = append(app.Flags, cli.BoolTFlag{
  107. Name: "r, reboot",
  108. Usage: "reboot after shutdown",
  109. Destination: &rebootFlag,
  110. })
  111. // OR? maybe implement it as a `kexec` cli tool?
  112. app.Flags = append(app.Flags, cli.BoolFlag{
  113. Name: "kexec",
  114. Usage: "kexec the default RancherOS cfg",
  115. Destination: &kexecFlag,
  116. })
  117. app.Flags = append(app.Flags, cli.BoolFlag{
  118. Name: "kexec-previous",
  119. Usage: "kexec the previous RancherOS cfg",
  120. Destination: &previouskexecFlag,
  121. })
  122. app.Flags = append(app.Flags, cli.StringFlag{
  123. Name: "kexec-append",
  124. Usage: "kexec using the specified kernel boot params (ignores global.cfg)",
  125. Destination: &kexecAppendFlag,
  126. })
  127. } else {
  128. app.Flags = append(app.Flags, cli.BoolFlag{
  129. Name: "r, reboot",
  130. Usage: "reboot after shutdown",
  131. Destination: &rebootFlag,
  132. })
  133. }
  134. //TODO: add the time and msg flags...
  135. app.HideHelp = true
  136. app.Run(os.Args)
  137. }
  138. func Kexec(previous bool, bootDir, append string) error {
  139. cfg := "linux-current.cfg"
  140. if previous {
  141. cfg = "linux-previous.cfg"
  142. }
  143. cfgFile := filepath.Join(bootDir, cfg)
  144. vmlinuzFile, initrdFile, err := install.ReadSyslinuxCfg(cfgFile)
  145. if err != nil {
  146. log.Errorf("%s", err)
  147. return err
  148. }
  149. globalCfgFile := filepath.Join(bootDir, "global.cfg")
  150. if append == "" {
  151. append, err = install.ReadGlobalCfg(globalCfgFile)
  152. if err != nil {
  153. log.Errorf("%s", err)
  154. return err
  155. }
  156. }
  157. // TODO: read global.cfg if append == ""
  158. // kexec -l ${DIST}/vmlinuz --initrd=${DIST}/initrd --append="${kernelArgs} ${APPEND}" -f
  159. cmd := exec.Command(
  160. "kexec",
  161. "-l", vmlinuzFile,
  162. "--initrd", initrdFile,
  163. "--append", append,
  164. "-f")
  165. log.Debugf("Run(%#v)", cmd)
  166. cmd.Stderr = os.Stderr
  167. if _, err := cmd.Output(); err != nil {
  168. log.Errorf("Failed to kexec: %s", err)
  169. return err
  170. }
  171. log.Infof("kexec'd to new install")
  172. return nil
  173. }
  174. // Reboot is used by installation / upgrade
  175. // TODO: add kexec option
  176. func Reboot() {
  177. os.Args = []string{"reboot"}
  178. reboot("reboot", false, syscall.LINUX_REBOOT_CMD_RESTART)
  179. }
  180. func shutdown(c *cli.Context) error {
  181. // the shutdown command's default is poweroff
  182. var powerCmd uint
  183. powerCmd = syscall.LINUX_REBOOT_CMD_POWER_OFF
  184. if rebootFlag {
  185. powerCmd = syscall.LINUX_REBOOT_CMD_RESTART
  186. } else if poweroffFlag {
  187. powerCmd = syscall.LINUX_REBOOT_CMD_POWER_OFF
  188. } else if haltFlag {
  189. powerCmd = syscall.LINUX_REBOOT_CMD_HALT
  190. }
  191. timeArg := c.Args().Get(0)
  192. // We may be called via an absolute path, so check that now and make sure we
  193. // don't pass the wrong app name down. Aside from the logic in the immediate
  194. // context here, the container name is derived from how we were called and
  195. // cannot contain slashes.
  196. appName := filepath.Base(c.App.Name)
  197. if appName == "shutdown" && timeArg != "" {
  198. if timeArg != "now" {
  199. err := fmt.Errorf("Sorry, can't parse '%s' as time value (only 'now' supported)", timeArg)
  200. log.Error(err)
  201. return err
  202. }
  203. // TODO: if there are more params, LOG them
  204. }
  205. reboot(appName, forceFlag, powerCmd)
  206. return nil
  207. }