Build with C99
[khatus.git] / x5 / khatus.c
CommitLineData
77c76070
SK
1#include <fcntl.h>
2#include <unistd.h>
3#include <sys/select.h>
4d66492f 4
9b5ebc12
SK
5#include <assert.h>
6#include <ctype.h>
77c76070 7#include <errno.h>
9b5ebc12
SK
8#include <stdio.h>
9#include <stdlib.h>
10#include <string.h>
11
4d66492f
SK
12#define debug(args...) {fprintf(stderr, "[debug] " args);}
13#define info( args...) {fprintf(stderr, "[info] " args);}
14#define error(args...) {fprintf(stderr, "[error] " args);}
15#define fatal(args...) {fprintf(stderr, "[fatal] " args); exit(EXIT_FAILURE);}
16#define usage(args...) {print_usage(); fatal("[usage] " args);}
9b5ebc12 17
4d66492f 18char *argv0;
9b5ebc12 19
4d66492f
SK
20typedef struct File File;
21struct File {
22 char *name;
23 int fd;
9b5ebc12 24 int width;
4d66492f 25 int last_read;
9b5ebc12 26 int ttl;
4d66492f
SK
27 int pos; /* Position on the output buffer. */
28 File *next;
9b5ebc12
SK
29};
30
4d66492f
SK
31typedef struct Config Config;
32struct Config {
9b5ebc12 33 int interval;
4d66492f 34 char * separator;
4d66492f
SK
35 File * files;
36 int file_count;
37 int total_width;
38} defaults = {
39 .interval = 1,
40 .separator = "|",
4d66492f
SK
41 .files = NULL,
42 .file_count = 0,
43 .total_width = 0,
9b5ebc12
SK
44};
45
77c76070
SK
46void
47file_print_one(File *f)
48{
49 debug(
50 "File "
51 "{"
52 " name = %s,"
53 " fd = %d,"
54 " width = %d,"
55 " last_read = %d,"
56 " ttl = %d,"
57 " pos = %d,"
58 " next = %p,"
59 " }\n",
60 f->name,
61 f->fd,
62 f->width,
63 f->last_read,
64 f->ttl,
65 f->pos,
66 f->next
67 );
68}
69
70void
71file_print_all(File *head)
72{
73 for (File *f = head; f; f = f->next) {
74 file_print_one(f);
75 }
76}
77
78void
79config_print(Config *c)
80{
81 debug(
82 "Config "
83 "{"
84 " interval = %d,"
85 " separator = %s,"
86 " file_count = %d,"
87 " total_width = %d,"
88 " files = ..."
89 " }\n",
90 c->interval,
91 c->separator,
92 c->file_count,
93 c->total_width
94 );
95 file_print_all(c->files);
96}
9b5ebc12
SK
97
98int
99is_pos_num(char *s)
100{
101 while (*s != '\0')
102 if (!isdigit(*(s++)))
103 return 0;
104 return 1;
105}
106
107void
108print_usage()
109{
4d66492f 110 assert(argv0);
9b5ebc12
SK
111 fprintf(
112 stderr,
113 "\n"
4d66492f 114 "Usage: %s [OPTION ...] SPEC [SPEC ...]\n"
9b5ebc12 115 "\n"
4d66492f
SK
116 " SPEC = FILE_PATH DATA_WIDTH DATA_TTL\n"
117 " FILE_PATH = string\n"
118 " DATA_WIDTH = int (* (positive) number of characters *)\n"
119 " DATA_TTL = int (* (positive) number of seconds *)\n"
120 " OPTION = -i INTERVAL\n"
121 " | -s SEPARATOR\n"
122 " SEPARATOR = string\n"
123 " INTERVAL = int (* (positive) number of seconds *)\n"
9b5ebc12 124 "\n",
4d66492f 125 argv0
9b5ebc12
SK
126 );
127 fprintf(
128 stderr,
129 "Example: %s -i 1 /dev/shm/khatus/khatus_sensor_x 4 10\n"
130 "\n",
4d66492f 131 argv0
9b5ebc12
SK
132 );
133}
134
4d66492f 135void opts_parse_any(Config *, int, char *[], int); /* For mutually-recursive calls. */
9b5ebc12
SK
136
137void
4d66492f 138parse_opts_opt_i(Config *cfg, int argc, char *argv[], int i)
9b5ebc12
SK
139{
140 if (i < argc) {
141 char *param = argv[i++];
142
143 if (is_pos_num(param)) {
4d66492f
SK
144 cfg->interval = atoi(param);
145 opts_parse_any(cfg, argc, argv, i);
9b5ebc12
SK
146 } else {
147 usage("Option -i parameter is invalid: \"%s\"\n", param);
148 }
149 } else {
150 usage("Option -i parameter is missing.\n");
151 }
152}
153
154void
4d66492f
SK
155parse_opts_opt_s(Config *cfg, int argc, char *argv[], int i)
156{
157 if (i < argc) {
3c836bfd 158 cfg->separator = calloc((strlen(argv[i]) + 1), sizeof(char));
4d66492f
SK
159 strcpy(cfg->separator, argv[i]);
160 opts_parse_any(cfg, argc, argv, ++i);
161 } else {
162 usage("Option -s parameter is missing.\n");
163 }
164}
165
4d66492f
SK
166void
167parse_opts_opt(Config *cfg, int argc, char *argv[], int i)
9b5ebc12
SK
168{
169 switch (argv[i][1]) {
4d66492f
SK
170 case 'i': parse_opts_opt_i(cfg, argc, argv, ++i); break; /* TODO: Generic set_int */
171 case 's': parse_opts_opt_s(cfg, argc, argv, ++i); break; /* TODO: Generic set_str */
9b5ebc12
SK
172 default : usage("Option \"%s\" is invalid\n", argv[i]);
173 }
174}
175
176void
4d66492f 177parse_opts_spec(Config *cfg, int argc, char *argv[], int i)
9b5ebc12
SK
178{
179 if ((i + 3) > argc)
180 usage("[spec] Parameter(s) missing for file \"%s\".\n", argv[i]);
181
182 char *n = argv[i++];
183 char *w = argv[i++];
184 char *t = argv[i++];
185
186 if (!is_pos_num(w))
187 usage("[spec] Invalid width: \"%s\", for file \"%s\"\n", w, n);
188 if (!is_pos_num(t))
189 usage("[spec] Invalid TTL: \"%s\", for file \"%s\"\n", t, n);
3c836bfd 190 File *f = calloc(1, sizeof(struct File));
9b5ebc12 191 if (f) {
4d66492f
SK
192 f->name = n;
193 f->fd = -1;
194 f->width = atoi(w);
195 f->ttl = atoi(t);
196 f->last_read = 0;
197 f->pos = cfg->total_width;
198 f->next = cfg->files;
199
200 cfg->files = f;
201 cfg->total_width += f->width;
202 cfg->file_count++;
9b5ebc12 203 } else {
4d66492f 204 fatal("[memory] Allocation failure.");
9b5ebc12 205 }
4d66492f 206 opts_parse_any(cfg, argc, argv, i);
9b5ebc12
SK
207}
208
209void
4d66492f 210opts_parse_any(Config *cfg, int argc, char *argv[], int i)
9b5ebc12
SK
211{
212 if (i < argc) {
213 switch (argv[i][0]) {
4d66492f
SK
214 case '-': parse_opts_opt(cfg, argc, argv, i); break;
215 default : parse_opts_spec(cfg, argc, argv, i);
216 }
217 }
218}
219
220void
221opts_parse(Config *cfg, int argc, char *argv[], int i)
222{
223 opts_parse_any(cfg, argc, argv, 1);
224
225 File *last = cfg->files;
226 cfg->files = NULL;
227 for (File *f = last; f; ) {
228 File *next = f->next;
229 f->next = cfg->files;
230 cfg->files = f;
231 f = next;
232 }
233}
234
77c76070
SK
235void
236read_one(File *f, char *buf)
237{
238 ssize_t n;
239 char *b;
240
241 b = buf + f->pos;
242 memset(b, ' ', f->width);
243 while ((n = read(f->fd, b, f->width)) > 0) {
244 b += n;
245 debug("read %zd from %s\n", n, f->name);
246 }
247
248 if (n > -1) {
249 if (*(b - 1) == '\n')
250 *(b - 1) = ' ';
251 } else {
252 error(
253 "Failed to read: \"%s\". Error: %s\n",
254 f->name,
255 strerror(errno)
256 );
257 }
258
259 close(f->fd);
260 f->fd = -1;
261}
262
4d66492f
SK
263void
264read_all(Config *cfg, char *buf)
265{
77c76070
SK
266 fd_set fds;
267 int maxfd;
268 int ready;
269
270 FD_ZERO(&fds);
271
4d66492f
SK
272 /* TODO: stat then check TTL */
273 for (File *f = cfg->files; f; f = f->next) {
77c76070 274 debug("opening: %s\n", f->name);
4d66492f 275 if (f->fd < 0)
77c76070
SK
276 f->fd = open(f->name, O_RDONLY | O_NONBLOCK);
277 if (f->fd == -1)
4d66492f
SK
278 /* TODO: Consider backing off retries for failed files. */
279 fatal("Failed to open \"%s\"\n", f->name);
77c76070
SK
280 if (f->fd > maxfd)
281 maxfd = f->fd;
282 FD_SET(f->fd, &fds);
283 }
284 debug("selecting...\n");
285 ready = select(maxfd + 1, &fds, NULL, NULL, NULL);
286 debug("ready: %d\n", ready);
287 assert(ready != 0);
288 if (ready < 0)
289 fatal("%s", strerror(errno));
290 for (File *f = cfg->files; f; f = f->next) {
291 if (FD_ISSET(f->fd, &fds)) {
292 debug("reading: %s\n", f->name);
293 read_one(f, buf);
9b5ebc12
SK
294 }
295 }
296}
297
298int
4d66492f 299main(int argc, char *argv[])
9b5ebc12 300{
4d66492f
SK
301 int width;
302 int nfiles = 0;
303 int seplen;
304 int prefix = 0;
305 char *buf;
306 Config *cfg = &defaults;
307
308 argv0 = argv[0];
309
310 opts_parse(cfg, argc, argv, 1);
311 debug("argv0 = %s\n", argv0);
77c76070 312 config_print(cfg);
4d66492f 313 if (cfg->files == NULL)
9b5ebc12 314 usage("No file specs were given!\n");
4d66492f
SK
315
316 width = cfg->total_width;
317 seplen = strlen(cfg->separator);
318
319 /* 1st pass to make space for separators */
320 for (File *f = cfg->files; f; f = f->next) {
321 f->pos += prefix;
322 prefix += seplen;
323 nfiles++;
324 }
325 width += (seplen * (nfiles - 1));
3c836bfd 326 buf = calloc(1, width + 1);
4d66492f
SK
327 if (buf == NULL)
328 fatal("[memory] Failed to allocate buffer of %d bytes", width);
329 memset(buf, ' ', width);
330 buf[width] = '\0';
331 /* 2nd pass to set the separators */
332 for (File *f = cfg->files; f; f = f->next) {
333 if (f->pos) { /* Skip the first, left-most */
77c76070
SK
334 /* Copying only seplen ensures we omit the '\0' byte. */
335 strncpy(buf + (f->pos - seplen), cfg->separator, seplen);
4d66492f
SK
336 }
337 }
338
77c76070 339 printf("%s\n", buf);
4d66492f
SK
340 /* TODO: nanosleep and nano time diff */
341 for (;;) {
342 read_all(cfg, buf);
343 printf("%s\n", buf);
4d66492f 344 }
9b5ebc12 345}
This page took 0.042725 seconds and 4 git commands to generate.