1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
|
package runit
import (
"errors"
"fmt"
"io/ioutil"
"os"
"syscall"
"time"
)
const (
defaultServiceDir = "/etc/service"
taiOffset = 4611686018427387914
statusLen = 20
posTimeStart = 0
posTimeEnd = 7
posPidStart = 12
posPidEnd = 15
posWant = 17
posState = 19
StateDown = 0
StateUp = 1
StateFinish = 2
)
var (
ENoRunsv = errors.New("runsv not running")
StateToString = map[int]string{
StateDown: "down",
StateUp: "up",
StateFinish: "finish",
}
)
type SvStatus struct {
Pid int
Duration int
Timestamp time.Time
State int
NormallyUp bool
Want int
}
type service struct {
Name string
ServiceDir string
}
func GetServices(dir string) ([]*service, error) {
if dir == "" {
dir = defaultServiceDir
}
files, err := ioutil.ReadDir(dir)
if err != nil {
return nil, err
}
services := []*service{}
for _, file := range files {
if file.Mode()&os.ModeSymlink == os.ModeSymlink || file.IsDir() {
services = append(services, GetService(file.Name(), dir))
}
}
return services, nil
}
func GetService(name string, dir string) *service {
if dir == "" {
dir = defaultServiceDir
}
r := service{Name: name, ServiceDir: dir}
return &r
}
func (s *service) file(file string) string {
return fmt.Sprintf("%s/%s/supervise/%s", s.ServiceDir, s.Name, file)
}
func (s *service) runsvRunning() (bool, error) {
file, err := os.OpenFile(s.file("ok"), os.O_WRONLY, 0)
if err != nil {
if err == syscall.ENXIO {
return false, nil
}
return false, err
}
file.Close()
return true, nil
}
func (s *service) status() ([]byte, error) {
file, err := os.Open(s.file("status"))
if err != nil {
return nil, err
}
defer file.Close()
status := make([]byte, statusLen)
_, err = file.Read(status)
return status, err
}
func (s *service) NormallyUp() bool {
_, err := os.Stat(s.file("down"))
return err != nil
}
func (s *service) Status() (*SvStatus, error) {
status, err := s.status()
if err != nil {
return nil, err
}
var pid int
pid = int(status[posPidEnd])
for i := posPidEnd - 1; i >= posPidStart; i-- {
pid <<= 8
pid += int(status[i])
}
tai := int64(status[posTimeStart])
for i := posTimeStart + 1; i <= posTimeEnd; i++ {
tai <<= 8
tai += int64(status[i])
}
state := status[posState] // 0: down, 1: run, 2: finish
tv := &syscall.Timeval{}
if err := syscall.Gettimeofday(tv); err != nil {
return nil, err
}
sS := SvStatus{
Pid: pid,
Timestamp: time.Unix(tai-taiOffset, 0), // FIXME: do we just select the wrong slice?
Duration: int(int64(tv.Sec) - (tai - taiOffset)),
State: int(state),
NormallyUp: s.NormallyUp(),
}
switch status[posWant] {
case 'u':
sS.Want = StateUp
case 'd':
sS.Want = StateDown
}
return &sS, nil
}
|