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