ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/root-tail/root-tail.c
Revision: 1.6
Committed: Tue Oct 7 13:02:57 2003 UTC (20 years, 7 months ago) by pcg
Content type: text/plain
Branch: MAIN
Changes since 1.5: +1 -1 lines
Log Message:
*** empty log message ***

File Contents

# Content
1 /*
2 * Copyright 2001 by Marco d'Itri <md@linux.it>
3 *
4 * Original version by Mike Baker, then maintained by pcg@goof.com.
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
19 */
20
21 #include "config.h"
22 #include <stdlib.h>
23 #include <stdio.h>
24 #include <unistd.h>
25 #include <string.h>
26 #include <signal.h>
27 #include <time.h>
28 #include <fcntl.h>
29 #include <errno.h>
30 #include <sys/time.h>
31 #include <sys/stat.h>
32 #include <sys/types.h>
33 #if HAS_REGEX
34 #include <regex.h>
35 #endif
36 #include <X11/Xlib.h>
37 #include <X11/Xutil.h>
38
39 /* data structures */
40 struct logfile_entry {
41 char *fname; /* name of file */
42 char *desc; /* alternative description */
43 FILE *fp; /* FILE struct associated with file */
44 ino_t inode; /* inode of the file opened */
45 off_t last_size; /* file size at the last check */
46 unsigned long color; /* color to be used for printing */
47 struct logfile_entry *next;
48 };
49
50 struct linematrix {
51 char *line;
52 unsigned long color;
53 };
54
55
56 /* global variables */
57 int width = STD_WIDTH, listlen = STD_HEIGHT;
58 int win_x = LOC_X, win_y = LOC_Y;
59 int w = -1, h = -1, font_width, font_height, font_descent;
60 int do_reopen;
61 struct timeval interval = { 2, 400000 }; /* see Knuth */
62
63 /* command line options */
64 int opt_noinitial, opt_shade, opt_frame, opt_reverse, opt_nofilename,
65 geom_mask, reload = 3600;
66 const char *command = NULL,
67 *fontname = USE_FONT, *dispname = NULL, *def_color = DEF_COLOR;
68
69 struct logfile_entry *loglist = NULL, *loglist_tail = NULL;
70
71 Display *disp;
72 Window root;
73 GC WinGC;
74
75 #if HAS_REGEX
76 struct re_list {
77 regex_t from;
78 const char *to;
79 struct re_list *next;
80 };
81 struct re_list *re_head, *re_tail;
82 #endif
83
84
85 /* prototypes */
86 void list_files(int);
87 void force_reopen(int);
88 void force_refresh(int);
89 void blank_window(int);
90
91 void InitWindow(void);
92 unsigned long GetColor(const char *);
93 void redraw(void);
94 void refresh(struct linematrix *, int, int);
95
96 void transform_line(char *s);
97 int lineinput(char *, int, FILE *);
98 void reopen(void);
99 void check_open_files(void);
100 FILE *openlog(struct logfile_entry *);
101 void main_loop(void);
102
103 void display_version(void);
104 void display_help(char *);
105 void install_signal(int, void (*)(int));
106 void *xstrdup(const char *);
107 void *xmalloc(size_t);
108 int daemonize(void);
109
110 /* signal handlers */
111 void list_files(int dummy)
112 {
113 struct logfile_entry *e;
114
115 fprintf(stderr, "Files opened:\n");
116 for (e = loglist; e; e = e->next)
117 fprintf(stderr, "\t%s (%s)\n", e->fname, e->desc);
118 }
119
120 void force_reopen(int dummy)
121 {
122 do_reopen = 1;
123 }
124
125 void force_refresh(int dummy)
126 {
127 XClearWindow(disp, root);
128 redraw();
129 }
130
131 void blank_window(int dummy)
132 {
133 XClearWindow(disp, root);
134 XFlush(disp);
135 exit(0);
136 }
137
138 /* X related functions */
139 unsigned long GetColor(const char *ColorName)
140 {
141 XColor Color;
142 XWindowAttributes Attributes;
143
144 XGetWindowAttributes(disp, root, &Attributes);
145 Color.pixel = 0;
146 if (!XParseColor(disp, Attributes.colormap, ColorName, &Color))
147 fprintf(stderr, "can't parse %s\n", ColorName);
148 else if (!XAllocColor(disp, Attributes.colormap, &Color))
149 fprintf(stderr, "can't allocate %s\n", ColorName);
150 return Color.pixel;
151 }
152
153 void InitWindow(void)
154 {
155 XGCValues gcv;
156 Font font;
157 unsigned long gcm;
158 XFontStruct *info;
159 int screen, ScreenWidth, ScreenHeight;
160
161 if (!(disp = XOpenDisplay(dispname))) {
162 fprintf(stderr, "Can't open display %s.\n", dispname);
163 exit(1);
164 }
165 screen = DefaultScreen(disp);
166 ScreenHeight = DisplayHeight(disp, screen);
167 ScreenWidth = DisplayWidth(disp, screen);
168 root = RootWindow(disp, screen);
169 gcm = GCBackground;
170 gcv.graphics_exposures = True;
171 WinGC = XCreateGC(disp, root, gcm, &gcv);
172 XMapWindow(disp, root);
173 XSetForeground(disp, WinGC, GetColor(DEF_COLOR));
174
175 font = XLoadFont(disp, fontname);
176 XSetFont(disp, WinGC, font);
177 info = XQueryFont(disp, font);
178 font_width = info->max_bounds.width;
179 font_descent = info->max_bounds.descent;
180 font_height = info->max_bounds.ascent + font_descent;
181
182 w = width * font_width;
183 h = listlen * font_height;
184 if (geom_mask & XNegative)
185 win_x = win_x + ScreenWidth - w;
186 if (geom_mask & YNegative)
187 win_y = win_y + ScreenHeight - h;
188
189 XSelectInput(disp, root, ExposureMask|FocusChangeMask);
190 }
191
192 char *
193 detabificate (char *s)
194 {
195 char * out;
196 int i, j;
197
198 out = malloc (8 * strlen (s) + 1);
199
200 for(i = 0, j = 0; s[i]; i++)
201 {
202 if (s[i] == '\t')
203 do
204 out[j++] = ' ';
205 while (j % 8);
206 else
207 out[j++] = s[i];
208 }
209
210 out[j] = '\0';
211 return out;
212 }
213
214 /*
215 * redraw does a complete redraw, rather than an update (i.e. the area
216 * gets cleared first)
217 * the rest is handled by regular refresh()'es
218 */
219 void redraw(void)
220 {
221 XClearArea(disp, root, win_x, win_y, w, h + font_descent + 2, True);
222 }
223
224 /* Just redraw everything without clearing (i.e. after an EXPOSE event) */
225 void refresh(struct linematrix *lines, int miny, int maxy)
226 {
227 int lin;
228 int offset = (listlen + 1) * font_height;
229 unsigned long black_color = GetColor("black");
230
231 miny -= win_y + font_height;
232 maxy -= win_y - font_height;
233
234 for (lin = listlen; lin--;)
235 {
236 char *temp;
237
238 offset -= font_height;
239
240 if (offset < miny || offset > maxy)
241 continue;
242
243 #define LIN ((opt_reverse)?(listlen-lin-1):(lin))
244 temp = detabificate (lines[LIN].line);
245
246 if (opt_shade)
247 {
248 XSetForeground (disp, WinGC, black_color);
249 XDrawString (disp, root, WinGC, win_x + 2, win_y + offset + 2,
250 temp, strlen (temp));
251 }
252
253 XSetForeground (disp, WinGC, lines[LIN].color);
254 XDrawString (disp, root, WinGC, win_x, win_y + offset,
255 temp, strlen (temp));
256
257 free (temp);
258 }
259
260 if (opt_frame) {
261 int bot_y = win_y + h + font_descent + 2;
262
263 XDrawLine(disp, root, WinGC, win_x, win_y, win_x + w, win_y);
264 XDrawLine(disp, root, WinGC, win_x + w, win_y, win_x + w, bot_y);
265 XDrawLine(disp, root, WinGC, win_x + w, bot_y, win_x, bot_y);
266 XDrawLine(disp, root, WinGC, win_x, bot_y, win_x, win_y);
267 }
268 }
269
270 #if HAS_REGEX
271 void transform_line(char *s)
272 {
273 #ifdef I_AM_Md
274 int i;
275 if (1) {
276 for (i = 16; s[i]; i++)
277 s[i] = s[i + 11];
278 }
279 s[i + 1] = '\0';
280 #endif
281
282 if (transformre) {
283 int i;
284 regmatch_t matched[16];
285
286 i = regexec(&transformre, string, 16, matched, 0);
287 if (i == 0) { /* matched */
288 }
289 }
290 }
291 #endif
292
293
294 /*
295 * This routine should read 'width' characters and not more. However,
296 * we really want to read width + 1 charachters if the last char is a '\n',
297 * which we should remove afterwards. So, read width+1 chars and ungetc
298 * the last character if it's not a newline. This means 'string' must be
299 * width + 2 wide!
300 */
301 int lineinput(char *string, int slen, FILE *f)
302 {
303 int len;
304
305 do {
306 if (fgets(string, slen, f) == NULL) /* EOF or Error */
307 return 0;
308
309 len = strlen(string);
310 } while (len == 0);
311
312 if (string[len - 1] == '\n')
313 string[len - 1] = '\0'; /* erase newline */
314 else if (len >= slen - 1) {
315 ungetc(string[len - 1], f);
316 string[len - 1] = '\0';
317 }
318
319 #if HAS_REGEX
320 transform_line(string);
321 #endif
322 return len;
323 }
324
325 /* input: reads file->fname
326 * output: fills file->fp, file->inode
327 * returns file->fp
328 * in case of error, file->fp is NULL
329 */
330 FILE *openlog(struct logfile_entry *file)
331 {
332 struct stat stats;
333
334 if ((file->fp = fopen(file->fname, "r")) == NULL) {
335 file->fp = NULL;
336 return NULL;
337 }
338
339 fstat(fileno(file->fp), &stats);
340 if (S_ISFIFO(stats.st_mode)) {
341 if (fcntl(fileno(file->fp), F_SETFL, O_NONBLOCK) < 0)
342 perror("fcntl"), exit(1);
343 file->inode = 0;
344 } else
345 file->inode = stats.st_ino;
346
347 if (opt_noinitial)
348 fseek (file->fp, 0, SEEK_END);
349 else if (stats.st_size > (listlen + 1) * width)
350 {
351 char dummy[255];
352
353 fseek(file->fp, -((listlen + 2) * width), SEEK_END);
354 /* the pointer might point halfway some line. Let's
355 be nice and skip this damaged line */
356 lineinput(dummy, sizeof(dummy), file->fp);
357 }
358
359 file->last_size = stats.st_size;
360 return file->fp;
361 }
362
363 void reopen(void)
364 {
365 struct logfile_entry *e;
366
367 for (e = loglist; e; e = e->next) {
368 if (!e->inode)
369 continue; /* skip stdin */
370
371 if (e->fp)
372 fclose(e->fp);
373 /* if fp is NULL we will try again later */
374 openlog(e);
375 }
376
377 do_reopen = 0;
378 }
379
380 void check_open_files(void)
381 {
382 struct logfile_entry *e;
383 struct stat stats;
384
385 for (e = loglist; e; e = e->next) {
386 if (!e->inode)
387 continue; /* skip stdin */
388
389 if (stat(e->fname, &stats) < 0) { /* file missing? */
390 sleep(1);
391 if (e->fp)
392 fclose(e->fp);
393 if (openlog(e) == NULL)
394 break;
395 }
396
397 if (stats.st_ino != e->inode) { /* file renamed? */
398 if (e->fp)
399 fclose(e->fp);
400 if (openlog(e) == NULL)
401 break;
402 }
403
404 if (stats.st_size < e->last_size) { /* file truncated? */
405 fseek(e->fp, 0, SEEK_SET);
406 e->last_size = stats.st_size;
407 }
408 }
409 }
410
411 #define SCROLL_UP(lines, listlen) \
412 { \
413 int cur_line; \
414 for (cur_line = 0; cur_line < (listlen - 1); cur_line++) { \
415 strcpy(lines[cur_line].line, lines[cur_line + 1].line); \
416 lines[cur_line].color = lines[cur_line + 1].color; \
417 } \
418 }
419
420 void main_loop(void)
421 {
422 struct linematrix *lines = xmalloc(sizeof(struct linematrix) * listlen);
423 int lin, miny, maxy, buflen;
424 char *buf;
425 time_t lastreload;
426 Region region = XCreateRegion();
427 XEvent xev;
428
429 maxy = 0;
430 miny = win_y + h;
431 buflen = width + 2;
432 buf = xmalloc(buflen);
433 lastreload = time(NULL);
434
435 /* Initialize linematrix */
436 for (lin = 0; lin < listlen; lin++) {
437 lines[lin].line = xmalloc(buflen);
438 strcpy(lines[lin].line, "~");
439 lines[lin].color = GetColor(def_color);
440 }
441
442 if (!opt_noinitial)
443 while (lineinput(buf, buflen, loglist->fp) != 0) {
444 SCROLL_UP(lines, listlen);
445 /* print the next line */
446 strcpy(lines[listlen - 1].line, buf);
447 }
448
449 for (;;) {
450 int need_update = 0;
451 struct logfile_entry *current;
452 static struct logfile_entry *lastprinted = NULL;
453
454 /* read logs */
455 for (current = loglist; current; current = current->next) {
456 if (!current->fp)
457 continue; /* skip missing files */
458
459 clearerr(current->fp);
460
461 while (lineinput(buf, buflen, current->fp) != 0) {
462 /* print filename if any, and if last line was from
463 different file */
464 if (!opt_nofilename &&
465 !(lastprinted && lastprinted == current) &&
466 current->desc[0]) {
467 SCROLL_UP(lines, listlen);
468 sprintf(lines[listlen - 1].line, "[%s]", current->desc);
469 lines[listlen - 1].color = current->color;
470 }
471
472 SCROLL_UP(lines, listlen);
473 strcpy(lines[listlen - 1].line, buf);
474 lines[listlen - 1].color = current->color;
475
476 lastprinted = current;
477 need_update = 1;
478 }
479 }
480
481 if (need_update)
482 redraw();
483 else {
484 XFlush(disp);
485 if (!XPending(disp)) {
486 fd_set fdr;
487 struct timeval to = interval;
488
489 FD_ZERO(&fdr);
490 FD_SET(ConnectionNumber(disp), &fdr);
491 select(ConnectionNumber(disp) + 1, &fdr, 0, 0, &to);
492 }
493 }
494
495 check_open_files();
496
497 if (do_reopen)
498 reopen();
499
500 /* we ignore possible errors due to window resizing &c */
501 while (XPending(disp)) {
502 XNextEvent(disp, &xev);
503 switch (xev.type) {
504 case Expose:
505 {
506 XRectangle r;
507
508 r.x = xev.xexpose.x;
509 r.y = xev.xexpose.y;
510 r.width = xev.xexpose.width;
511 r.height = xev.xexpose.height;
512 XUnionRectWithRegion(&r, region, region);
513 if (miny > r.y)
514 miny = r.y;
515 if (maxy < r.y + r.height)
516 maxy = r.y + r.height;
517 }
518 break;
519 default:
520 #ifdef DEBUGMODE
521 fprintf(stderr, "PANIC! Unknown event %d\n", xev.type);
522 #endif
523 break;
524 }
525 }
526
527 /* reload if requested */
528 if (reload && lastreload + reload < time(NULL)) {
529 if (command)
530 system(command);
531
532 reopen();
533 lastreload = time(NULL);
534 }
535
536 if (!XEmptyRegion(region)) {
537 XSetRegion(disp, WinGC, region);
538 refresh(lines, miny, maxy);
539 XDestroyRegion(region);
540 region = XCreateRegion();
541 maxy = 0;
542 miny = win_y + h;
543 }
544 }
545 }
546
547
548 int main(int argc, char *argv[])
549 {
550 int i;
551 int opt_daemonize = 0;
552 #if HAS_REGEX
553 char *transform = NULL;
554 #endif
555
556 /* window needs to be initialized before colorlookups can be done */
557 /* just a dummy to get the color lookups right */
558 geom_mask = NoValue;
559 InitWindow();
560
561 for (i = 1; i < argc; i++) {
562 if (argv[i][0] == '-' && argv[i][1] != '\0' && argv[i][1] != ',') {
563 if (!strcmp(argv[i], "--?") ||
564 !strcmp(argv[i], "--help") || !strcmp(argv[i], "-h"))
565 display_help(argv[0]);
566 else if (!strcmp(argv[i], "-V"))
567 display_version();
568 else if (!strcmp(argv[i], "-g") || !strcmp(argv[i], "-geometry"))
569 geom_mask = XParseGeometry(argv[++i],
570 &win_x, &win_y, &width, &listlen);
571 else if (!strcmp(argv[i], "-display"))
572 dispname = argv[++i];
573 else if (!strcmp(argv[i], "-font") || !strcmp(argv[i], "-fn"))
574 fontname = argv[++i];
575 #if HAS_REGEX
576 else if (!strcmp(argv[i], "-t"))
577 transform = argv[++i];
578 #endif
579 else if (!strcmp(argv[i], "-fork") || !strcmp(argv[i], "-f"))
580 opt_daemonize = 1;
581 else if (!strcmp(argv[i], "-reload")) {
582 reload = atoi(argv[++i]);
583 command = argv[++i];
584 }
585 else if (!strcmp(argv[i], "-shade"))
586 opt_shade = 1;
587 else if (!strcmp(argv[i], "-frame"))
588 opt_frame = 1;
589 else if (!strcmp(argv[i], "-no-filename"))
590 opt_nofilename = 1;
591 else if (!strcmp(argv[i], "-reverse"))
592 opt_reverse = 1;
593 else if (!strcmp(argv[i], "-color"))
594 def_color = argv[++i];
595 else if (!strcmp(argv[i], "-noinitial"))
596 opt_noinitial = 1;
597 else if (!strcmp(argv[i], "-interval") || !strcmp(argv[i], "-i")) {
598 double iv = atof(argv[++i]);
599
600 interval.tv_sec = (int) iv;
601 interval.tv_usec = (iv - interval.tv_sec) * 1e6;
602 } else {
603 fprintf(stderr, "Unknown option '%s'.\n"
604 "Try --help for more information.\n", argv[i]);
605 exit(1);
606 }
607 } else { /* it must be a filename */
608 struct logfile_entry *e;
609 const char *fname, *desc, *fcolor = def_color;
610 char *p;
611
612 /* this is not foolproof yet (',' in filenames are not allowed) */
613 fname = desc = argv[i];
614 if ((p = strchr(argv[i], ','))) {
615 *p = '\0';
616 fcolor = p + 1;
617
618 if ((p = strchr(fcolor, ','))) {
619 *p = '\0';
620 desc = p + 1;
621 }
622 }
623
624 e = xmalloc(sizeof(struct logfile_entry));
625 if (argv[i][0] == '-' && argv[i][1] == '\0') {
626 if ((e->fp = fdopen(0, "r")) == NULL)
627 perror("fdopen"), exit(1);
628 if (fcntl(0, F_SETFL, O_NONBLOCK) < 0)
629 perror("fcntl"), exit(1);
630 e->fname = NULL;
631 e->inode = 0;
632 e->desc = xstrdup("stdin");
633 } else {
634 int l;
635
636 e->fname = xstrdup(fname);
637 if (openlog(e) == NULL)
638 perror(fname), exit(1);
639
640 l = strlen(desc);
641 if (l > width - 2) /* must account for [ ] */
642 l = width - 2;
643 e->desc = xmalloc(l + 1);
644 memcpy(e->desc, desc, l);
645 *(e->desc + l) = '\0';
646 }
647
648 e->color = GetColor(fcolor);
649 e->next = NULL;
650
651 if (!loglist)
652 loglist = e;
653 if (loglist_tail)
654 loglist_tail->next = e;
655 loglist_tail = e;
656 }
657 }
658
659 if (!loglist) {
660 fprintf(stderr, "You did not specify any files to tail\n"
661 "use %s --help for help\n", argv[0]);
662 exit(1);
663 }
664
665 #if HAS_REGEX
666 if (transform) {
667 int i;
668
669 transformre = xmalloc(sizeof(transformre));
670 i = regcomp(&transformre, transform, REG_EXTENDED);
671 if (i != 0) {
672 char buf[512];
673
674 regerror(i, &transformre, buf, sizeof(buf));
675 fprintf(stderr, "Cannot compile regular expression: %s\n", buf);
676 }
677 }
678 #endif
679
680 InitWindow();
681
682 install_signal(SIGINT, blank_window);
683 install_signal(SIGQUIT, blank_window);
684 install_signal(SIGTERM, blank_window);
685 install_signal(SIGHUP, force_reopen);
686 install_signal(SIGUSR1, list_files);
687 install_signal(SIGUSR2, force_refresh);
688
689 if (opt_daemonize)
690 daemonize();
691
692 main_loop();
693
694 exit(1); /* to make gcc -Wall stop complaining */
695 }
696
697 void install_signal(int sig, void (*handler)(int))
698 {
699 struct sigaction action;
700
701 action.sa_handler = handler;
702 sigemptyset(&action.sa_mask);
703 action.sa_flags = SA_RESTART;
704 if (sigaction(sig, &action, NULL) < 0)
705 fprintf(stderr, "sigaction(%d): %s\n", sig, strerror(errno)), exit(1);
706 }
707
708 void *xstrdup(const char *string)
709 {
710 void *p;
711
712 while ((p = strdup(string)) == NULL) {
713 fprintf(stderr, "Memory exausted.");
714 sleep(10);
715 }
716 return p;
717 }
718
719 void *xmalloc(size_t size)
720 {
721 void *p;
722
723 while ((p = malloc(size)) == NULL) {
724 fprintf(stderr, "Memory exausted.");
725 sleep(10);
726 }
727 return p;
728 }
729
730 void display_help(char *myname)
731 {
732 printf("Usage: %s [options] file1[,color[,desc]] "
733 "[file2[,color[,desc]] ...]\n", myname);
734 printf(" -g | -geometry geometry -g WIDTHxHEIGHT+X+Y\n"
735 " -color color use color $color as default\n"
736 " -reload sec command reload after $sec and run command\n"
737 " by default -- 3 mins\n"
738 " -font FONTSPEC (-fn) font to use\n"
739 " -f | -fork fork into background\n"
740 " -reverse print new lines at the top\n"
741 " -shade add shading to font\n"
742 " -noinitial don't display the last file lines on\n"
743 " startup\n"
744 " -i | -interval seconds interval between checks (fractional\n"
745 " values o.k.). Default 3\n"
746 " -V display version information and exit\n"
747 "\n");
748 printf("Example:\n%s -g 80x25+100+50 -font fixed /var/log/messages,green "
749 "/var/log/secure,red,'ALERT'\n", myname);
750 exit(0);
751 }
752
753 void display_version(void) {
754 printf("root-tail version " VERSION "\n");
755 exit(0);
756 }
757
758 int daemonize(void) {
759 switch (fork()) {
760 case -1:
761 return -1;
762 case 0:
763 break;
764 default:
765 _exit(0);
766 }
767
768 if (setsid() == -1)
769 return -1;
770
771 return 0;
772 }
773