1 /*----------------------------------------------------------------------------*/
2 /* Xymon monitor library. */
3 /* */
4 /* This is a library module, part of libxymon. */
5 /* It contains routines for reading configuration files with "include"s. */
6 /* */
7 /* Copyright (C) 2002-2011 Henrik Storner <henrik@storner.dk> */
8 /* */
9 /* This program is released under the GNU General Public License (GPL), */
10 /* version 2. See the file "COPYING" for details. */
11 /* */
12 /*----------------------------------------------------------------------------*/
13
14 static char rcsid[] = "$Id: stackio.c 8084 2019-08-30 23:01:18Z jccleaver $";
15
16 #include <sys/types.h>
17 #include <sys/stat.h>
18 #include <ctype.h>
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <string.h>
22 #include <limits.h>
23 #include <dirent.h>
24
25 #include "libxymon.h"
26
27 typedef struct filelist_t {
28 char *filename;
29 time_t mtime;
30 size_t fsize;
31 struct filelist_t *next;
32 } filelist_t;
33
34 typedef struct fgetsbuf_t {
35 FILE *fd;
36 char inbuf[4096+1];
37 char *inbufp;
38 int moretoread;
39 struct fgetsbuf_t *next;
40 } fgetsbuf_t;
41 static fgetsbuf_t *fgetshead = NULL;
42
43 typedef struct stackfd_t {
44 FILE *fd;
45 filelist_t **listhead;
46 struct stackfd_t *next;
47 } stackfd_t;
48 static stackfd_t *fdhead = NULL;
49 static char *stackfd_base = NULL;
50 static char *stackfd_mode = NULL;
51 static htnames_t *fnlist = NULL;
52
53 /*
54 * initfgets() and unlimfgets() implements a fgets() style
55 * input routine that can handle arbitrarily long input lines.
56 * Buffer space for the input is dynamically allocated and
57 * expanded, until the input hits a newline character.
58 * Simultaneously, lines ending with a '\' character are
59 * merged into one line, allowing for transparent handling
60 * of very long lines.
61 *
62 * This interface is also used by the stackfgets() routine.
63 *
64 * If you open a file directly, after getting the FILE
65 * descriptor call initfgets(FILE). Then use unlimfgets()
66 * to read data one line at a time. You must read until
67 * unlimfgets() returns NULL (at which point you should not
68 * call unlimfgets() again with this fd).
69 */
initfgets(FILE * fd)70 int initfgets(FILE *fd)
71 {
72 fgetsbuf_t *newitem;
73
74 newitem = (fgetsbuf_t *)malloc(sizeof(fgetsbuf_t));
75 *(newitem->inbuf) = '\0';
76 newitem->inbufp = newitem->inbuf;
77 newitem->moretoread = 1;
78 newitem->fd = fd;
79 newitem->next = fgetshead;
80 fgetshead = newitem;
81 return 0;
82 }
83
unlimfgets(strbuffer_t * buffer,FILE * fd)84 char *unlimfgets(strbuffer_t *buffer, FILE *fd)
85 {
86 fgetsbuf_t *fg;
87 size_t n;
88 char *eoln = NULL;
89
90 for (fg = fgetshead; (fg && (fg->fd != fd)); fg = fg->next) ;
91 if (!fg) {
92 errprintf("umlimfgets() called with bad input FD\n");
93 return NULL;
94 }
95
96 /* End of file ? */
97 if (!(fg->moretoread) && (*(fg->inbufp) == '\0')) {
98 if (fg == fgetshead) {
99 fgetshead = fgetshead->next;
100 free(fg);
101 }
102 else {
103 fgetsbuf_t *prev;
104 for (prev = fgetshead; (prev->next != fg); prev = prev->next) ;
105 prev->next = fg->next;
106 free(fg);
107 }
108 return NULL;
109 }
110
111 /* Make sure the output buffer is empty */
112 clearstrbuffer(buffer);
113
114 while (!eoln && (fg->moretoread || *(fg->inbufp))) {
115 int continued = 0;
116
117 if (*(fg->inbufp)) {
118 /* Have some data in the buffer */
119 eoln = strchr(fg->inbufp, '\n');
120 if (eoln) {
121 /* See if there's a continuation character just before the eoln */
122 char *contchar = eoln-1;
123 while ((contchar > fg->inbufp) && isspace((int)*contchar) && (*contchar != '\\')) contchar--;
124 continued = (*contchar == '\\');
125
126 if (continued) {
127 *contchar = '\0';
128 addtobuffer(buffer, fg->inbufp);
129 fg->inbufp = eoln+1;
130 eoln = NULL;
131 }
132 else {
133 char savech = *(eoln+1);
134 *(eoln+1) = '\0';
135 addtobuffer(buffer, fg->inbufp);
136 *(eoln+1) = savech;
137 fg->inbufp = eoln+1;
138 }
139 }
140 else {
141 /* No newline in buffer, so add all of it to the output buffer */
142 addtobuffer(buffer, fg->inbufp);
143
144 /* Input buffer is now empty */
145 *(fg->inbuf) = '\0';
146 fg->inbufp = fg->inbuf;
147 }
148 }
149
150 if (!eoln && !continued) {
151 /* Get data for the input buffer */
152 char *inpos = fg->inbuf;
153 size_t insize = sizeof(fg->inbuf);
154
155 /* If the last byte we read was a continuation char, we must do special stuff.
156 *
157 * Mike Romaniw discovered that if we hit an input with a newline exactly at
158 * the point of a buffer refill, then strlen(*buffer) is 0, and contchar then
159 * points before the start of the buffer. Bad. But this can only happen when
160 * the previous char WAS a newline, and hence it is not a continuation line.
161 * So the simple fix is to only do the cont-char stuff if **buffer is not NUL.
162 * Hence the test for both *buffer and **buffer.
163 */
164 if (STRBUF(buffer) && *STRBUF(buffer)) {
165 char *contchar = STRBUF(buffer) + STRBUFLEN(buffer) - 1;
166 while ((contchar > STRBUF(buffer)) && isspace((int)*contchar) && (*contchar != '\\')) contchar--;
167
168 if (*contchar == '\\') {
169 /*
170 * Remove the cont. char from the output buffer, and stuff it into
171 * the input buffer again - so we can check if there's a new-line coming.
172 */
173 strbufferchop(buffer, 1);
174 *(fg->inbuf) = '\\';
175 inpos++;
176 insize--;
177 }
178 }
179
180 n = fread(inpos, 1, insize-1, fd);
181 *(inpos + n) = '\0';
182 fg->inbufp = fg->inbuf;
183 if (n < insize-1) fg->moretoread = 0;
184 }
185 }
186
187 return STRBUF(buffer);
188 }
189
stackfopen(char * filename,char * mode,void ** v_listhead)190 FILE *stackfopen(char *filename, char *mode, void **v_listhead)
191 {
192 FILE *newfd;
193 stackfd_t *newitem;
194 char stackfd_filename[PATH_MAX];
195 filelist_t **listhead = (filelist_t **)v_listhead;
196
197 MEMDEFINE(stackfd_filename);
198
199 if (fdhead == NULL) {
200 char *p;
201
202 stackfd_base = strdup(filename);
203 p = strrchr(stackfd_base, '/'); if (p) *(p+1) = '\0';
204
205 stackfd_mode = strdup(mode);
206
207 strncpy(stackfd_filename, filename, sizeof(stackfd_filename));
208 }
209 else {
210 if (*filename == '/')
211 strncpy(stackfd_filename, filename, sizeof(stackfd_filename));
212 else
213 snprintf(stackfd_filename, sizeof(stackfd_filename), "%s/%s", stackfd_base, filename);
214 }
215
216 dbgprintf("Opening file %s\n", stackfd_filename);
217 newfd = fopen(stackfd_filename, stackfd_mode);
218 if (newfd != NULL) {
219 newitem = (stackfd_t *) malloc(sizeof(stackfd_t));
220 newitem->fd = newfd;
221 newitem->listhead = listhead;
222 newitem->next = fdhead;
223 fdhead = newitem;
224 initfgets(newfd);
225
226 if (listhead) {
227 struct filelist_t *newlistitem;
228 struct stat st;
229
230 fstat(fileno(newfd), &st);
231 newlistitem = (filelist_t *)malloc(sizeof(filelist_t));
232 newlistitem->filename = strdup(stackfd_filename);
233 newlistitem->mtime = st.st_mtime;
234 newlistitem->fsize = st.st_size;
235 newlistitem->next = *listhead;
236 *listhead = newlistitem;
237 }
238 }
239
240 MEMUNDEFINE(stackfd_filename);
241
242 return newfd;
243 }
244
245
stackfclose(FILE * fd)246 int stackfclose(FILE *fd)
247 {
248 int result;
249 stackfd_t *olditem;
250
251 if (fd != NULL) {
252 /* Close all */
253 while (fdhead != NULL) {
254 olditem = fdhead;
255 fdhead = fdhead->next;
256 fclose(olditem->fd);
257 xfree(olditem);
258 }
259 xfree(stackfd_base);
260 xfree(stackfd_mode);
261 while (fnlist) {
262 htnames_t *tmp = fnlist;
263 fnlist = fnlist->next;
264 xfree(tmp->name); xfree(tmp);
265 }
266 result = 0;
267 }
268 else {
269 olditem = fdhead;
270 fdhead = fdhead->next;
271 result = fclose(olditem->fd);
272 xfree(olditem);
273 }
274
275 return result;
276 }
277
stackfmodified(void * v_listhead)278 int stackfmodified(void *v_listhead)
279 {
280 /* Walk the list of filenames, and see if any have changed */
281 filelist_t *walk;
282 struct stat st;
283
284 for (walk=(filelist_t *)v_listhead; (walk); walk = walk->next) {
285 if (stat(walk->filename, &st) == -1) {
286 dbgprintf("File %s no longer exists\n", walk->filename);
287 return 1; /* File has disappeared */
288 }
289 if (st.st_mtime != walk->mtime) {
290 dbgprintf("File %s new timestamp\n", walk->filename);
291 return 1; /* Timestamp has changed */
292 }
293 if (S_ISREG(st.st_mode) && (st.st_size != walk->fsize)) {
294 dbgprintf("File %s new size\n", walk->filename);
295 return 1; /* Size has changed */
296 }
297 }
298
299 return 0;
300 }
301
stackfclist(void ** v_listhead)302 void stackfclist(void **v_listhead)
303 {
304 /* Free the list of filenames */
305 filelist_t *tmp;
306
307 if ((v_listhead == NULL) || (*v_listhead == NULL)) return;
308
309 while (*v_listhead) {
310 tmp = (filelist_t *) *v_listhead;
311 *v_listhead = ((filelist_t *) *v_listhead)->next;
312 xfree(tmp->filename);
313 xfree(tmp);
314 }
315 *v_listhead = NULL;
316 }
317
namecompare(const void * v1,const void * v2)318 static int namecompare(const void *v1, const void *v2)
319 {
320 char **n1 = (char **)v1;
321 char **n2 = (char **)v2;
322
323 /* Sort in reverse order, so when we add them to the list in LIFO, it will be alpha-sorted */
324 return -strcmp(*n1, *n2);
325 }
326
addtofnlist(char * dirname,int is_optional,void ** v_listhead)327 static void addtofnlist(char *dirname, int is_optional, void **v_listhead)
328 {
329 filelist_t **listhead = (filelist_t **)v_listhead;
330 DIR *dirfd;
331 struct dirent *d;
332 struct stat st;
333 char dirfn[PATH_MAX], fn[PATH_MAX];
334 char **fnames = NULL;
335 int fnsz = 0;
336 int i;
337
338 if (*dirname == '/') strncpy(dirfn, dirname, sizeof(dirfn)); else snprintf(dirfn, sizeof(dirfn), "%s/%s", stackfd_base, dirname);
339
340 if ((dirfd = opendir(dirfn)) == NULL) {
341 if (!is_optional) errprintf("WARNING: Cannot open directory %s\n", dirfn);
342 else dbgprintf("addtofnlist(): Cannot open directory %s\n", dirfn);
343 return;
344 }
345
346 /* Add the directory itself to the list of files we watch for modifications */
347 if (listhead) {
348 filelist_t *newlistitem;
349
350 stat(dirfn, &st);
351 newlistitem = (filelist_t *)malloc(sizeof(filelist_t));
352 newlistitem->filename = strdup(dirfn);
353 newlistitem->mtime = st.st_mtime;
354 newlistitem->fsize = 0; /* We don't check sizes of directories */
355 newlistitem->next = *listhead;
356 *listhead = newlistitem;
357 }
358
359 while ((d = readdir(dirfd)) != NULL) {
360 int fnlen = strlen(d->d_name);
361
362 /* Skip all dot-files */
363 if (*(d->d_name) == '.') continue;
364
365 /* Skip editor backups - file ending with '~' */
366 if (*(d->d_name + fnlen - 1) == '~') continue;
367
368 /* Skip RCS files - they end with ",v" */
369 if ((fnlen >= 2) && (strcmp(d->d_name + fnlen - 2, ",v") == 0)) continue;
370
371 /* Skip any documentation file starting with README */
372 if (strncmp(d->d_name, "README", 6) == 0) continue;
373
374 /* Skip Debian installer left-overs - they end with ".dpkg-new" or .dpkg-orig */
375 if ((fnlen >= 9) && (strcmp(d->d_name + fnlen - 9, ".dpkg-new") == 0)) continue;
376 if ((fnlen >= 10) && (strcmp(d->d_name + fnlen - 10, ".dpkg-orig") == 0)) continue;
377
378
379 /* Skip RPM package debris - they end with ".rpmsave", .rpmnew, or .rpmorig */
380 if ((fnlen >= 8) && ((strcmp(d->d_name + fnlen - 8, ".rpmsave") == 0) || (strcmp(d->d_name + fnlen - 8, ".rpmorig") == 0) ) ) continue;
381 if ((fnlen >= 7) && (strcmp(d->d_name + fnlen - 7, ".rpmnew") == 0)) continue;
382
383 #if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)
384 #pragma GCC diagnostic push
385 #pragma GCC diagnostic ignored "-Wformat-truncation"
386 #endif // __GNUC__
387 snprintf(fn, sizeof(fn), "%s/%s", dirfn, d->d_name);
388 #if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)
389 #pragma GCC diagnostic pop
390 #endif // __GNUC__
391 if (stat(fn, &st) == -1) continue;
392
393 if (S_ISDIR(st.st_mode)) {
394 /* Skip RCS sub-directories */
395 if (strcmp(d->d_name, "RCS") == 0) continue;
396 addtofnlist(fn, 0, v_listhead); /* this directory is optional, but opening up files that do exist isn't */
397 }
398
399 /* Skip everything that isn't a regular file */
400 if (!S_ISREG(st.st_mode)) continue;
401
402 if (fnsz == 0) fnames = (char **)malloc(2*sizeof(char **));
403 else fnames = (char **)realloc(fnames, (fnsz+2)*sizeof(char **));
404 fnames[fnsz] = strdup(fn);
405
406 fnsz++;
407 }
408
409 closedir(dirfd);
410
411 if (fnsz) {
412 qsort(fnames, fnsz, sizeof(char *), namecompare);
413 for (i=0; (i<fnsz); i++) {
414 htnames_t *newitem = malloc(sizeof(htnames_t));
415 newitem->name = fnames[i];
416 newitem->next = fnlist;
417 fnlist = newitem;
418 }
419
420 xfree(fnames);
421 }
422 }
423
stackfgets(strbuffer_t * buffer,char * extraincl)424 char *stackfgets(strbuffer_t *buffer, char *extraincl)
425 {
426 char *result;
427 int optional = 0;
428
429 result = unlimfgets(buffer, fdhead->fd);
430
431 if (result) {
432 char *bufpastwhitespace = STRBUF(buffer) + strspn(STRBUF(buffer), " \t");
433 if (strncmp(bufpastwhitespace, "optional", 8) == 0) { optional = 1; bufpastwhitespace += 8 + strspn(bufpastwhitespace+8, " \t"); }
434
435 if ( (strncmp(bufpastwhitespace, "include ", 8) == 0) || (strncmp(bufpastwhitespace, "include\t", 8) == 0) ||
436 (extraincl && (strncmp(bufpastwhitespace, extraincl, strlen(extraincl)) == 0)) ) {
437 char *newfn, *eol, eolchar = '\0';
438
439 eol = bufpastwhitespace + strcspn(bufpastwhitespace, "\r\n"); if (eol) { eolchar = *eol; *eol = '\0'; }
440 newfn = bufpastwhitespace + strcspn(bufpastwhitespace, " \t");
441 newfn += strspn(newfn, " \t");
442 while (*newfn && isspace(*(newfn + strlen(newfn) - 1))) *(newfn + strlen(newfn) -1) = '\0';
443
444 if (*newfn && (stackfopen(newfn, "r", (void **)fdhead->listhead) != NULL))
445 return stackfgets(buffer, extraincl);
446 else {
447 if (!optional) errprintf("WARNING: Cannot open include file '%s', line was: %s\n", newfn, STRBUF(buffer));
448 else dbgprintf("stackfgets(): Cannot open include file '%s', line was: %s\n", newfn, STRBUF(buffer));
449
450 if (eol) *eol = eolchar;
451 return result;
452 }
453 }
454 else if ((strncmp(bufpastwhitespace, "directory ", 10) == 0) || (strncmp(bufpastwhitespace, "directory\t", 10) == 0)) {
455 char *dirfn, *eol, eolchar = '\0';
456
457 eol = bufpastwhitespace + strcspn(bufpastwhitespace, "\r\n"); if (eol) { eolchar = *eol; *eol = '\0'; }
458 dirfn = bufpastwhitespace + 9;
459 dirfn += strspn(dirfn, " \t");
460 while (*dirfn && isspace(*(dirfn + strlen(dirfn) - 1))) *(dirfn + strlen(dirfn) -1) = '\0';
461
462 if (*dirfn) addtofnlist(dirfn, optional, (void **)fdhead->listhead);
463 if (fnlist && (stackfopen(fnlist->name, "r", (void **)fdhead->listhead) != NULL)) {
464 htnames_t *tmp = fnlist;
465
466 fnlist = fnlist->next;
467 xfree(tmp->name); xfree(tmp);
468 return stackfgets(buffer, extraincl);
469 }
470 else if (fnlist) {
471 htnames_t *tmp = fnlist;
472
473 if (!optional) errprintf("WARNING: Cannot open include file '%s', line was: %s\n", fnlist->name, buffer);
474 else dbgprintf("stackfgets(): Cannot open include file '%s', line was: %s\n", fnlist->name, buffer);
475
476 fnlist = fnlist->next;
477 xfree(tmp->name); xfree(tmp);
478 if (eol) *eol = eolchar;
479 return result;
480 }
481 else {
482 /* Empty directory include - return a blank line */
483 dbgprintf("stackfgets(): Directory %s was empty\n", dirfn);
484 *result = '\0';
485 return result;
486 }
487 }
488 }
489 else if (result == NULL) {
490 /* end-of-file on read */
491 stackfclose(NULL);
492 if (fnlist) {
493 if (stackfopen(fnlist->name, "r", (void **)fdhead->listhead) != NULL) {
494 htnames_t *tmp = fnlist;
495
496 fnlist = fnlist->next;
497 xfree(tmp->name); xfree(tmp);
498 return stackfgets(buffer, extraincl);
499 }
500 else {
501 htnames_t *tmp = fnlist;
502
503 if (!optional) errprintf("WARNING: Cannot open include file '%s', line was: %s\n", fnlist->name, buffer);
504 else dbgprintf("stackfgets(): Cannot open include file '%s', line was: %s\n", fnlist->name, buffer);
505
506 fnlist = fnlist->next;
507 xfree(tmp->name); xfree(tmp);
508 return result;
509 }
510 }
511 else if (fdhead != NULL)
512 return stackfgets(buffer, extraincl);
513 else
514 return NULL;
515 }
516
517 return result;
518 }
519
520
521 #ifdef STANDALONE
main(int argc,char * argv[])522 int main(int argc, char *argv[])
523 {
524 char *fn, *p;
525 char cmd[1024];
526 FILE *fd;
527 strbuffer_t *inbuf = newstrbuffer(0);
528 void *listhead = NULL;
529 int done, linenum;
530
531 fn = strdup(argv[1]);
532 strncpy(cmd, "!", sizeof(cmd));
533 done = 0;
534 while (!done) {
535 if (*cmd == '!') {
536 fd = stackfopen(fn, "r", &listhead);
537 linenum = 1;
538 if (!fd) { errprintf("Cannot open file %s\n", fn); continue; }
539
540 while (stackfgets(inbuf, NULL)) {
541 linenum++;
542 printf("%s", STRBUF(inbuf));
543 }
544 stackfclose(fd);
545 }
546 else if (*cmd == '?') {
547 filelist_t *walk = (filelist_t *)listhead;
548
549 while (walk) {
550 printf("%s %lu\n", walk->filename, (unsigned long)walk->fsize);
551 walk = walk->next;
552 }
553 if (stackfmodified(listhead)) printf("File(s) have been modified\n");
554 else printf("No changes\n");
555 }
556 else if (*cmd == '.') {
557 done = 1;
558 continue;
559 }
560 else {
561 xfree(fn); fn = strdup(cmd);
562 stackfclist(&listhead);
563 strncpy(cmd, "!", sizeof(cmd));
564 continue;
565 }
566
567 printf("\nCmd: "); fflush(stdout);
568 if (fgets(cmd, sizeof(cmd), stdin)) {
569 p = strchr(cmd, '\n'); if (p) *p = '\0';
570 }
571 else
572 done = 1;
573 }
574
575 xfree(fn);
576 stackfclist(&listhead);
577
578 return 0;
579 }
580 #endif
581
582