unix/fiss

src/fsvc/fsvc.c in master
Repositories | Summary | Log | Files | LICENSE

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}