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