main.go 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. package main
  2. import (
  3. "log"
  4. "os"
  5. "os/exec"
  6. "os/signal"
  7. "path/filepath"
  8. "strings"
  9. "syscall"
  10. )
  11. const etcService = "/etc/service"
  12. func main() {
  13. log.SetFlags(0)
  14. runsvdir, err := exec.LookPath("runsvdir")
  15. if err != nil {
  16. log.Fatal(err)
  17. }
  18. sv, err := exec.LookPath("sv")
  19. if err != nil {
  20. log.Fatal(err)
  21. }
  22. if fi, err := os.Stat(etcService); err != nil {
  23. log.Fatal(err)
  24. } else if !fi.IsDir() {
  25. log.Fatalf("%s is not a directory", etcService)
  26. }
  27. if pid := os.Getpid(); pid != 1 {
  28. log.Printf("warning: I'm not PID 1, I'm PID %d", pid)
  29. }
  30. go reapAll()
  31. supervisor := cmd(runsvdir, etcService)
  32. if err := supervisor.Start(); err != nil {
  33. log.Fatal(err)
  34. }
  35. log.Printf("%s started", runsvdir)
  36. go shutdown(sv, supervisor.Process)
  37. if err := supervisor.Wait(); err != nil {
  38. log.Printf("%s exited with error: %v", runsvdir, err)
  39. } else {
  40. log.Printf("%s exited cleanly", runsvdir)
  41. }
  42. }
  43. func reapAll() {
  44. c := make(chan os.Signal)
  45. signal.Notify(c, syscall.SIGCHLD)
  46. for range c {
  47. go reapOne()
  48. }
  49. }
  50. // From https://github.com/ramr/go-reaper/blob/master/reaper.go
  51. func reapOne() {
  52. var (
  53. ws syscall.WaitStatus
  54. pid int
  55. err error
  56. )
  57. for {
  58. pid, err = syscall.Wait4(-1, &ws, 0, nil)
  59. if err != syscall.EINTR {
  60. break
  61. }
  62. }
  63. if err == syscall.ECHILD {
  64. return
  65. }
  66. log.Printf("reaped child process %d (%+v)", pid, ws)
  67. }
  68. type signaler interface {
  69. Signal(os.Signal) error
  70. }
  71. func shutdown(sv string, s signaler) {
  72. c := make(chan os.Signal)
  73. signal.Notify(c, syscall.SIGTERM, syscall.SIGINT)
  74. sig := <-c
  75. log.Printf("received %s", sig)
  76. matches, err := filepath.Glob(filepath.Join(etcService, "*"))
  77. if err != nil {
  78. log.Printf("when shutting down services: %v", err)
  79. return
  80. }
  81. var stopped []string
  82. for _, match := range matches {
  83. fi, err := os.Stat(match)
  84. if err != nil {
  85. log.Printf("%s: %v", match, err)
  86. continue
  87. }
  88. if !fi.IsDir() {
  89. log.Printf("%s: not a directory", match)
  90. continue
  91. }
  92. service := filepath.Base(match)
  93. stop := cmd(sv, "stop", service)
  94. if err := stop.Run(); err != nil {
  95. log.Printf("%s: %v", strings.Join(stop.Args, " "), err)
  96. continue
  97. }
  98. stopped = append(stopped, service)
  99. }
  100. log.Printf("stopped %d: %s", len(stopped), strings.Join(stopped, ", "))
  101. log.Printf("stopping supervisor with signal %s...", sig)
  102. if err := s.Signal(sig); err != nil {
  103. log.Print(err)
  104. }
  105. log.Printf("shutdown handler exiting")
  106. }
  107. func cmd(path string, args ...string) *exec.Cmd {
  108. return &exec.Cmd{
  109. Path: path,
  110. Args: append([]string{path}, args...),
  111. Env: os.Environ(),
  112. Stdin: os.Stdin,
  113. Stdout: os.Stdout,
  114. Stderr: os.Stderr,
  115. }
  116. }