ztatus.c
1#include <dirent.h>
2#include <errno.h>
3#include <fcntl.h>
4#include <signal.h>
5#include <stdarg.h>
6#include <stdio.h>
7#include <stdlib.h>
8#include <string.h>
9#include <sys/select.h>
10#include <sys/stat.h>
11#include <time.h>
12#include <unistd.h>
13
14#include "config.h"
15
16#define PNAME "ztatus"
17
18/* functions */
19static int check_proc(struct dirent*);
20static int die(const char*, ...);
21static void dispatch(void);
22static int get_int(const char*, int*);
23static int get_pid(void);
24static void notify(const char*);
25static void render_time(char*);
26static int run(void);
27static void set_status(const char*);
28static void sigint_handler(int);
29
30/* global variables */
31static char *last_line;
32static int delay_time = 0;
33static int running = 0;
34static int self_pid = 0;
35static int fifofd;
36
37void
38sigint_handler(int signum)
39{
40 running = 0;
41}
42
43int
44die(const char *format, ...)
45{
46 va_list args;
47
48 va_start(args, format);
49 vfprintf(stderr, format, args);
50 va_end(args);
51
52 return 1;
53}
54
55int
56run(void)
57{
58 int rv, count, pid;
59 fd_set rfds;
60 struct timeval tv;
61 char *status_line = NULL;
62
63 /* only allow one instance of ztatus */
64 if ((pid = get_pid()) != -1)
65 return die("error: an instance of %s is already running (PID: %d)\n",
66 PNAME, pid);
67
68 unlink(FIFO_PATH);
69
70 /* setup, or fail */
71 if ((mknod(FIFO_PATH, S_IFIFO | 0600, 0)) < 0)
72 return die("error: cannot create fifo '%s'\n", FIFO_PATH);
73 if ((fifofd = open(FIFO_PATH, O_RDWR | O_NONBLOCK)) < 0)
74 return die("error: cannot open fifo '%s'\n", FIFO_PATH);
75 if ((status_line = malloc(STATUS_LENGTH)) == NULL)
76 return die("error: failed to allocate memory\n", FIFO_PATH);
77 if ((last_line = calloc(STATUS_LENGTH, sizeof(char))) == NULL)
78 return die("error: failed to allocate memory\n", FIFO_PATH);
79
80 /* handle interrupts and terminations */
81 signal(SIGINT, sigint_handler);
82 signal(SIGTERM, sigint_handler);
83
84 fprintf(stderr, "%s is running as PID: %d\n", PNAME, self_pid);
85
86 running = 1;
87 while (running) {
88 count++;
89
90 /* wait for 1 second or fifofd */
91 FD_ZERO(&rfds);
92 FD_SET(fifofd, &rfds);
93 tv.tv_sec = 1;
94 tv.tv_usec = 0;
95
96 /* select fifo descriptor, and break on failure */
97 if ((rv = select(fifofd + 1, &rfds, NULL, NULL, &tv)) < 0)
98 break;
99
100 /* dispatch on fifo read */
101 if (FD_ISSET(fifofd, &rfds))
102 dispatch();
103
104 /* prevent data printing if it's delayed.
105 * this is so an external signal can borrow the status line
106 * for a short time, determined by the delay_time */
107 if (delay_time > 0) {
108 delay_time--;
109 continue;
110 }
111
112 render_time(status_line);
113 set_status(status_line);
114 }
115
116 /* free up resources when done */
117 free(status_line);
118 free(last_line);
119 if (fifofd >= 0)
120 unlink(FIFO_PATH);
121
122 return 0;
123}
124
125int
126get_pid(void)
127{
128 const char *path = "/proc";
129 DIR *dir;
130 struct dirent *e;
131 int p = -1;
132
133 /* check all processes */
134 if ((dir = opendir(path)) != NULL) {
135 while ((e = readdir(dir)) != NULL && (p = check_proc(e)) == -1);
136 closedir(dir);
137 }
138
139 return p;
140}
141
142int
143get_int(const char *s, int *i)
144{
145 const char *c = s;
146
147 *i = 0;
148 while (*c) {
149 if (*c < 48 || *c >= 58)
150 return 0;
151 *i = *i * 10 - 48 + *c;
152 c++;
153 }
154
155 return 1;
156}
157
158int
159check_proc(struct dirent *e)
160{
161 char *base_p = NULL;
162 char *comm_p = NULL;
163 char *cmdline_p = NULL;
164 char comm[7] = {0};
165 char cmdline[64] = {0};
166 FILE *fptr = NULL;
167 struct stat sb;
168 int i, l;
169 int pid;
170 int state = 0;
171
172 if (get_int(e->d_name, &pid) == 0 || pid == self_pid)
173 state = -1;
174
175 if (state == 0) {
176 base_p = malloc(strlen(e->d_name) + 7);
177 strcpy(base_p, "/proc/");
178 strcat(base_p, e->d_name);
179 if (stat(base_p, &sb) != 0 || !S_ISDIR(sb.st_mode))
180 state = -1;
181 }
182
183 if (state == 0) {
184 comm_p = malloc(strlen(base_p) + 6);
185 strcpy(comm_p, base_p);
186 strcat(comm_p, "/comm");
187 if (stat(comm_p, &sb) != 0 || !S_ISREG(sb.st_mode))
188 state = -1;
189 }
190
191 if (state == 0) {
192 cmdline_p = malloc(strlen(base_p) + 9);
193 strcpy(cmdline_p, base_p);
194 strcat(cmdline_p, "/cmdline");
195 if (stat(cmdline_p, &sb) != 0 || !S_ISREG(sb.st_mode))
196 state = -1;
197 }
198
199 if (state == 0) {
200 fptr = fopen(comm_p, "rb");
201 for (i = 0, l = sizeof(comm); i < l; i++)
202 comm[i] = fgetc(fptr);
203 fclose(fptr);
204 if (comm[6] != '\n') {
205 state = -1;
206 } else {
207 comm[6] = '\0';
208 if (strcmp(comm, PNAME) != 0)
209 state = -1;
210 }
211 }
212
213 if (state == 0) {
214 fptr = fopen(cmdline_p, "rb");
215 fread(cmdline, sizeof(cmdline), 1, fptr);
216 l = ftell(fptr);
217 fclose(fptr);
218 if (strchr(cmdline, '/') != NULL)
219 memmove(cmdline,
220 strrchr(cmdline, '/') + 1,
221 strlen(strrchr(cmdline, '/') + 1)
222 );
223 if (strcmp(cmdline, PNAME) == 0)
224 return pid;
225 }
226
227 if (base_p != NULL)
228 free(base_p);
229 if (comm_p != NULL)
230 free(comm_p);
231 if (cmdline_p != NULL)
232 free(cmdline_p);
233
234 return -1;
235}
236
237void
238render_time(char *str)
239{
240 int z, y, era, time_val;
241 unsigned doe, yoe, doy, mp, d, m;
242 char *tmp = malloc(32);
243 time_t currentTime = time(NULL);
244 struct tm *localTime;
245
246 localTime = localtime(¤tTime);
247 time_val = currentTime + localTime->tm_gmtoff;
248
249 z = time_val / 86400 + 719468;
250 era = (z >= 0 ? z : z - 146096) / 146097;
251 doe = (unsigned)(z - era * 146097);
252 yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
253 y = (int)yoe + era * 400;
254 doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
255 mp = (5 * doy + 2) / 153;
256 d = doy - (153 * mp + 2) / 5 + 1;
257 m = mp + (mp < 10 ? 3 : -9);
258 y += (m <= 2);
259
260 snprintf(tmp, 32, FORMAT_TIME
261 ,y ,m ,d
262 ,(time_val / 3600) % 24
263 ,(time_val / 60) % 60
264 );
265 strcpy(str, tmp);
266 free(tmp);
267}
268
269void
270set_status(const char *line) {
271 if (strcmp(line, last_line))
272 puts(line);
273 strcpy(last_line, line);
274}
275
276void
277dispatch(void)
278{
279 char buf[BUFSIZ];
280 char *line, *next;
281 ssize_t n;
282
283 if ((n = read(fifofd, buf, sizeof(buf) - 1)) == -1)
284 return;
285
286 buf[n] = '\0';
287 line = buf;
288
289 /* read each line as a single notification,
290 * which means that only that last line will be seen */
291 while (line) {
292 next = strchr(line, '\n');
293 if (next)
294 *next = '\0';
295 notify(line);
296 if (next)
297 *next = '\n';
298 line = next ? next + 1 : NULL;
299 }
300
301 /* make sure fifo is empty */
302 while (errno != EWOULDBLOCK)
303 read(fifofd, buf, sizeof(buf) - 1);
304}
305
306void
307notify(const char* input)
308{
309 char *status_line = NULL;
310
311 if ((status_line = malloc(STATUS_LENGTH)) == NULL)
312 return;
313
314 delay_time = (int)(DELAY_INTERVAL);
315
316 snprintf(status_line, STATUS_LENGTH, FORMAT_NOTIFY, input);
317 set_status(status_line);
318 free(status_line);
319}
320
321int
322main(int argc, char *argv[])
323{
324 char *s;
325 self_pid = getpid();
326
327 if ((s = strrchr(argv[0], '/')) == NULL)
328 s = argv[0];
329 else
330 s++;
331
332 if (strcmp(s, PNAME) != 0)
333 return die("error: program must run as '%s'\n", PNAME);
334
335 return run();
336}