fsvc.c (7819B) download
1
2#include "../fsvs/message.h"
3#include "../fsvs/service.h"
4#include "config.h"
5#include "signame.h"
6#include "util.h"
7
8#include <errno.h>
9#include <fcntl.h>
10#include <getopt.h>
11#include <stdbool.h>
12#include <stdint.h>
13#include <string.h>
14#include <sys/stat.h>
15#include <unistd.h>
16
17
18const char* current_prog(void) {
19 return "fsvc";
20}
21
22static const char* const command_names[][2] = {
23 { "up", "u" }, // starts the services, pin as started
24 { "down", "d" }, // stops the service, pin as stopped
25 { "once", "o" }, // same as xup
26 { "term", "t" }, // same as down
27 { "kill", "k" }, // sends kill, pin as stopped
28 { "pause", "p" }, // pauses the service
29 { "cont", "c" }, // resumes the service
30 { "reset", "r" }, // resets the service
31 { "alarm", "a" }, // sends alarm
32 { "hup", "h" }, // sends hup
33 { "int", "i" }, // sends interrupt
34 { "quit", "q" }, // sends quit
35 { "1", "1" }, // sends usr1
36 { "2", "2" }, // sends usr2
37 { "usr1", "1" }, // sends usr1
38 { "usr2", "2" }, // sends usr2
39 { "exit", "x" }, // does nothing
40 { "restart", "!du" }, // restarts the service, don't modify pin
41 { "start", "!u" }, // start the service, pin as started, print status
42 { "stop", "!d" }, // stop the service, pin as stopped, print status
43 { "status", "!" }, // print status
44 { "check", "!" }, // print status
45 { "enable", "!.e" }, // enable service
46 { "disable", "!.d" }, // disable service
47 { 0, 0 }
48};
49
50static const struct option long_options[] = {
51 { "version", no_argument, NULL, 'V' },
52 { "wait", no_argument, NULL, 'w' },
53 { 0 }
54};
55
56struct service_decode {
57 int state;
58 pid_t pid;
59 time_t state_change;
60 bool restart;
61 bool once;
62 bool is_depends;
63 bool wants_up;
64 int last_exit;
65 int return_code;
66 uint8_t fail_count;
67 bool paused;
68 bool is_terminating;
69};
70
71static void decode(struct service_decode* s, const struct service_serial* buffer) {
72 uint64_t tai = ((uint64_t) buffer->status_change[0] << 56) |
73 ((uint64_t) buffer->status_change[1] << 48) |
74 ((uint64_t) buffer->status_change[2] << 40) |
75 ((uint64_t) buffer->status_change[3] << 32) |
76 ((uint64_t) buffer->status_change[4] << 24) |
77 ((uint64_t) buffer->status_change[5] << 16) |
78 ((uint64_t) buffer->status_change[6] << 8) |
79 ((uint64_t) buffer->status_change[7] << 0);
80
81 s->state_change = tai - 4611686018427387914ULL;
82
83 s->state = buffer->state;
84 s->return_code = buffer->return_code;
85 s->fail_count = buffer->fail_count;
86 s->is_terminating = (buffer->flags >> 4) & 0x01;
87 s->once = (buffer->flags >> 3) & 0x01;
88 s->restart = (buffer->flags >> 2) & 0x01;
89 s->last_exit = (buffer->flags >> 0) & 0x03;
90
91 s->pid = (buffer->pid[0] << 0) |
92 (buffer->pid[1] << 8) |
93 (buffer->pid[2] << 16) |
94 (buffer->pid[3] << 24);
95
96 s->paused = buffer->paused;
97 s->wants_up = buffer->restart == 'u';
98
99 s->is_depends = s->wants_up != (s->once || s->restart);
100}
101
102static time_t get_mtime(int dir) {
103 struct stat st;
104 if (fstatat(dir, "supervise/status", &st, 0) == -1)
105 return -1;
106 return st.st_mtim.tv_sec;
107}
108
109static int handle_command(int dir, char command) {
110 // no custom commands defined
111
112 (void) dir, (void) command;
113
114 return -1;
115}
116
117static int send_command(int dir, const char* command) {
118 int fd;
119 if ((fd = openat(dir, "supervise/control", O_WRONLY | O_NONBLOCK)) == -1)
120 return -1;
121
122 for (const char* c = command; *c != '\0'; c++) {
123 if (*c == '.') {
124 c++;
125 if (handle_command(dir, *c) == -1)
126 return -1;
127 } else {
128 if (write(fd, c, 1) == -1)
129 break;
130 }
131 }
132 close(fd);
133
134 return 0;
135}
136
137int status(int dir) {
138 int fd;
139 time_t timeval;
140 const char* timeunit = "sec";
141 struct service_serial buffer;
142 struct service_decode s;
143
144 if ((fd = openat(dir, "supervise/status", O_RDONLY | O_NONBLOCK)) == -1)
145 return -1;
146
147 if (read(fd, &buffer, sizeof(buffer)) == -1) {
148 close(fd);
149 return -1;
150 }
151
152 close(fd);
153
154 decode(&s, &buffer);
155
156 timeval = time(NULL) - s.state_change;
157
158 if (timeval >= 60) {
159 timeval /= 60;
160 timeunit = "min";
161 if (timeval >= 60) {
162 timeval /= 60;
163 timeunit = "h";
164 if (timeval >= 24) {
165 timeval /= 24;
166 timeunit = "d";
167 }
168 }
169 }
170
171 switch (s.state) {
172 case STATE_SETUP:
173 print("setting up");
174 break;
175 case STATE_STARTING:
176 print("starting as %d", s.pid);
177 break;
178 case STATE_ACTIVE_FOREGROUND:
179 print("active as %d", s.pid);
180 break;
181 case STATE_ACTIVE_BACKGROUND:
182 case STATE_ACTIVE_DUMMY:
183 print("active");
184 break;
185 case STATE_FINISHING:
186 print("finishing as %d", s.pid);
187 break;
188 case STATE_STOPPING:
189 print("stopping as %d", s.pid);
190 break;
191 case STATE_INACTIVE:
192 print("inactive");
193 break;
194 case STATE_ERROR:
195 print("dead (error)");
196 break;
197 }
198
199 if (s.paused)
200 print(" & paused");
201
202 print(" since %lu%s", timeval, timeunit);
203
204 if (s.once == S_ONCE)
205 print(", started once");
206
207 if (s.restart)
208 print(", should restart");
209
210 if (s.is_depends)
211 print(", started as dependency");
212
213 if (s.return_code > 0 && s.last_exit == EXIT_NORMAL)
214 print(", exited with %d", s.return_code);
215
216 if (s.return_code > 0 && s.last_exit == EXIT_SIGNALED)
217 print(", crashed with SIG%s", sigabbr(s.return_code));
218
219 if (s.fail_count > 0)
220 print(", failed %d times", s.fail_count);
221
222 print("\n");
223
224 return 0;
225}
226
227int main(int argc, char** argv) {
228 int opt, dir, fd,
229 timeout = SV_STATUS_WAIT;
230 time_t mod, start;
231
232 const char* command = NULL;
233 const char* service;
234
235 while ((opt = getopt_long(argc, argv, ":Vw:", long_options, NULL)) != -1) {
236 switch (opt) {
237 case 'V':
238 // version
239 break;
240 case 'w':
241 timeout = parse_long(optarg, "seconds");
242 break;
243 default:
244 case '?':
245 if (optopt)
246 fprint(1, "error: invalid option -%c\n", optopt);
247 else
248 fprint(1, "error: invalid option %s\n", argv[optind - 1]);
249 print_usage_exit(PROG_FSVC, 1);
250 }
251 }
252
253 argc -= optind, argv += optind;
254
255 if (argc == 0) {
256 fprint(1, "error: command omitted\n");
257 print_usage_exit(PROG_FSVC, 1);
258 }
259 for (const char** ident = (void*) command_names; ident[0] != NULL; ident++) {
260 if (streq(ident[0], argv[0])) {
261 command = ident[1];
262 break;
263 }
264 }
265 if (command == NULL) {
266 fprint(1, "error: unknown command '%s'\n", argv[0]);
267 print_usage_exit(PROG_FSVC, 1);
268 }
269
270 argc--, argv++;
271
272 if (argc == 0) {
273 fprint(1, "error: at least one service must be specified\n");
274 print_usage_exit(PROG_FSVC, 1);
275 }
276
277 chdir(SV_SERVICE_DIR);
278
279 bool print_status;
280 if ((print_status = command[0] == '!')) {
281 command++;
282 }
283
284 for (int i = 0; i < argc; i++) {
285 service = progname(argv[i]);
286
287 if ((dir = open(argv[i], O_DIRECTORY)) == -1) {
288 fprint(1, "error: %s: cannot open directory: %s\n", argv[i], strerror(errno));
289 continue;
290 }
291
292 if ((fd = openat(dir, "supervise/ok", O_WRONLY | O_NONBLOCK)) == -1) {
293 fprint(1, "error: %s: cannot open supervise/control: %s\n", argv[i], strerror(errno));
294 continue;
295 }
296 close(fd);
297
298 if ((mod = get_mtime(dir)) == -1) {
299 fprint(1, "error: %s: cannot get modify-time\n", argv[i]);
300 continue;
301 }
302
303 if (command[0] != '\0') {
304 if (send_command(dir, command) == -1) {
305 fprint(1, "error: %s: unable to send command\n", argv[i]);
306 continue;
307 }
308 } else {
309 mod++; // avoid modtime timeout
310 }
311
312 start = time(NULL);
313 if (print_status) {
314 while (get_mtime(dir) == mod && time(NULL) - start < timeout)
315 usleep(500); // sleep half a secound
316
317 if (get_mtime(dir) == mod)
318 print("timeout: ");
319
320 print("%s: ", service);
321
322 if (status(dir) == -1)
323 print("unable to access supervise/status\n");
324 }
325 }
326}