ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/root-tail/root-tail.c
Revision: 1.28
Committed: Mon Mar 29 17:25:51 2004 UTC (22 years, 5 months ago) by chris_moore
Content type: text/plain
Branch: MAIN
Changes since 1.27: +98 -38 lines
Log Message:
Only redraw the lines which have changed when -noflicker is in
effect.  This reduces flicker further.

File Contents

# User Rev Content
1 pcg 1.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 pcg 1.7 #include <locale.h>
34 pcg 1.11 #include <ctype.h>
35     #include <stdarg.h>
36 chris_moore 1.26 #include <X11/Xlib.h>
37     #include <X11/Xatom.h>
38     #include <X11/Xutil.h>
39    
40 pcg 1.1 #if HAS_REGEX
41     #include <regex.h>
42     #endif
43    
44 chris_moore 1.27 #define SHADE_X 2
45     #define SHADE_Y 2
46    
47 pcg 1.1 /* data structures */
48 pcg 1.11 struct logfile_entry
49     {
50     struct logfile_entry *next;
51    
52     char *fname; /* name of file */
53     char *desc; /* alternative description */
54     char *buf; /* text read but not yet displayed */
55     FILE *fp; /* FILE struct associated with file */
56     ino_t inode; /* inode of the file opened */
57     off_t last_size; /* file size at the last check */
58     unsigned long color; /* color to be used for printing */
59     int partial; /* true if the last line isn't complete */
60     int lastpartial; /* true if the previous output wasn't complete */
61     int index; /* index into linematrix of a partial line */
62 chris_moore 1.28 int modified; /* true if line is modified & needs displaying */
63 pcg 1.1 };
64    
65 pcg 1.11 struct linematrix
66     {
67     char *line;
68 pcg 1.12 int len;
69 pcg 1.11 unsigned long color;
70 pcg 1.1 };
71    
72 chris_moore 1.28 struct displaymatrix
73     {
74     char *line;
75     int len;
76     int buffer_size;
77     };
78    
79 pcg 1.1 /* global variables */
80 pcg 1.12 struct linematrix *lines;
81 chris_moore 1.28 struct displaymatrix *display;
82 pcg 1.11 int width = STD_WIDTH, height = STD_HEIGHT, listlen;
83 pcg 1.1 int win_x = LOC_X, win_y = LOC_Y;
84 pcg 1.22 int font_ascent, font_height;
85 chris_moore 1.27 int effect_x_space, effect_y_space; /* how much space does shading / outlining take up */
86     int effect_x_offset, effect_y_offset; /* and how does it offset the usable space */
87 pcg 1.1 int do_reopen;
88 pcg 1.21 struct timeval interval = { 2, 400000 };
89 pcg 1.7 XFontSet fontset;
90 pcg 1.1
91     /* command line options */
92 pcg 1.5 int opt_noinitial, opt_shade, opt_frame, opt_reverse, opt_nofilename,
93 chris_moore 1.24 opt_outline, opt_noflicker, opt_whole, opt_update, opt_wordwrap,
94     geom_mask, reload = 0;
95 pcg 1.1 const char *command = NULL,
96 pcg 1.11 *fontname = USE_FONT, *dispname = NULL, *def_color = DEF_COLOR,
97     *continuation = "[+]";
98 pcg 1.1
99     struct logfile_entry *loglist = NULL, *loglist_tail = NULL;
100    
101     Display *disp;
102     Window root;
103     GC WinGC;
104    
105     #if HAS_REGEX
106 pcg 1.11 struct re_list
107     {
108     regex_t from;
109     const char *to;
110     struct re_list *next;
111 pcg 1.1 };
112     struct re_list *re_head, *re_tail;
113 chris_moore 1.26 char *transform_to = NULL;
114     regex_t *transformre;
115 pcg 1.1 #endif
116    
117    
118     /* prototypes */
119 pcg 1.11 void list_files (int);
120     void force_reopen (int);
121     void force_refresh (int);
122     void blank_window (int);
123    
124     void InitWindow (void);
125     unsigned long GetColor (const char *);
126 chris_moore 1.28 void redraw (int);
127     void refresh (int, int, int, int);
128 pcg 1.11
129     void transform_line (char *s);
130     int lineinput (struct logfile_entry *);
131     void reopen (void);
132     void check_open_files (void);
133     FILE *openlog (struct logfile_entry *);
134 pcg 1.12 static void main_loop (void);
135 pcg 1.11
136     void display_version (void);
137     void display_help (char *);
138     void install_signal (int, void (*)(int));
139     void *xstrdup (const char *);
140     void *xmalloc (size_t);
141 chris_moore 1.28 void *xrealloc (void *, size_t);
142 pcg 1.11 int daemonize (void);
143 pcg 1.1
144     /* signal handlers */
145 pcg 1.11 void
146     list_files (int dummy)
147 pcg 1.1 {
148 pcg 1.11 struct logfile_entry *e;
149 pcg 1.1
150 pcg 1.11 fprintf (stderr, "Files opened:\n");
151     for (e = loglist; e; e = e->next)
152     fprintf (stderr, "\t%s (%s)\n", e->fname, e->desc);
153 pcg 1.1 }
154    
155 pcg 1.11 void
156     force_reopen (int dummy)
157 pcg 1.1 {
158 pcg 1.11 do_reopen = 1;
159 pcg 1.1 }
160    
161 pcg 1.11 void
162     force_refresh (int dummy)
163 pcg 1.1 {
164 chris_moore 1.28 redraw (1);
165 pcg 1.1 }
166    
167 pcg 1.11 void
168     blank_window (int dummy)
169 pcg 1.1 {
170 pcg 1.19 XClearArea (disp, root, win_x - 2, win_y - 2, width + 5, height + 5, False);
171 pcg 1.11 XFlush (disp);
172     exit (0);
173 pcg 1.1 }
174    
175     /* X related functions */
176 pcg 1.11 unsigned long
177     GetColor (const char *ColorName)
178 pcg 1.1 {
179 pcg 1.11 XColor Color;
180     XWindowAttributes Attributes;
181 pcg 1.1
182 pcg 1.11 XGetWindowAttributes (disp, root, &Attributes);
183     Color.pixel = 0;
184 pcg 1.8
185 pcg 1.11 if (!XParseColor (disp, Attributes.colormap, ColorName, &Color))
186     fprintf (stderr, "can't parse %s\n", ColorName);
187     else if (!XAllocColor (disp, Attributes.colormap, &Color))
188     fprintf (stderr, "can't allocate %s\n", ColorName);
189 pcg 1.8
190 pcg 1.11 return Color.pixel;
191 pcg 1.1 }
192    
193 pcg 1.11 static Window
194     root_window (Display * display, int screen_number)
195 pcg 1.7 {
196 pcg 1.11 Atom SWM_VROOT = XInternAtom (display, "__SWM_VROOT", False);
197 pcg 1.7 Window real_root_window = RootWindow (display, screen_number);
198    
199 pcg 1.11 if (root) /* root window set via option */
200 pcg 1.8 return root;
201    
202 pcg 1.11 if (SWM_VROOT != None)
203 pcg 1.7 {
204     Window unused, *windows;
205     unsigned int count;
206    
207     if (XQueryTree (display, real_root_window, &unused, &unused, &windows,
208 pcg 1.11 &count))
209     {
210     int i;
211    
212     for (i = 0; i < count; i++)
213     {
214     Atom type;
215     int format;
216     unsigned long nitems, bytes_after_return;
217     unsigned char *virtual_root_window;
218    
219     if (XGetWindowProperty (display, windows[i], SWM_VROOT,
220     0, 1, False, XA_WINDOW, &type, &format,
221     &nitems, &bytes_after_return,
222     &virtual_root_window) == Success)
223     {
224     if (type != None)
225     {
226     if (type == XA_WINDOW)
227     {
228     XFree (windows);
229     return (Window) virtual_root_window;
230     }
231     else
232     fprintf (stderr,
233     "__SWM_VROOT property type mismatch");
234     }
235     }
236     else
237     fprintf (stderr,
238     "failed to get __SWM_VROOT property on window 0x%lx",
239     windows[i]);
240     }
241    
242     if (count)
243     XFree (windows);
244     }
245 pcg 1.7 else
246 pcg 1.11 fprintf (stderr, "Can't query tree on root window 0x%lx",
247     real_root_window);
248 pcg 1.7 }
249     else
250     /* This shouldn't happen. The Xlib documentation is wrong BTW. */
251     fprintf (stderr, "Can't intern atom __SWM_VROOT");
252    
253     return real_root_window;
254     }
255    
256 pcg 1.11 void
257     InitWindow (void)
258 pcg 1.1 {
259 pcg 1.11 XGCValues gcv;
260     unsigned long gcm;
261     int screen, ScreenWidth, ScreenHeight;
262    
263     if (!(disp = XOpenDisplay (dispname)))
264     {
265     fprintf (stderr, "Can't open display %s.\n", dispname);
266     exit (1);
267     }
268    
269     screen = DefaultScreen (disp);
270     ScreenHeight = DisplayHeight (disp, screen);
271     ScreenWidth = DisplayWidth (disp, screen);
272    
273     root = root_window (disp, screen);
274    
275     gcm = GCBackground;
276     gcv.graphics_exposures = True;
277     WinGC = XCreateGC (disp, root, gcm, &gcv);
278     XMapWindow (disp, root);
279     XSetForeground (disp, WinGC, GetColor (DEF_COLOR));
280    
281     {
282     char **missing_charset_list;
283     int missing_charset_count;
284     char *def_string;
285    
286     fontset = XCreateFontSet (disp, fontname,
287     &missing_charset_list, &missing_charset_count,
288     &def_string);
289    
290     if (missing_charset_count)
291     {
292     fprintf (stderr,
293     "Missing charsets in String to FontSet conversion (%s)\n",
294     missing_charset_list[0]);
295     XFreeStringList (missing_charset_list);
296     }
297     }
298    
299     if (!fontset)
300     {
301     fprintf (stderr, "unable to create fontset, exiting.\n");
302     exit (1);
303     }
304    
305     {
306     XFontSetExtents *xfe = XExtentsOfFontSet (fontset);
307    
308     font_height = xfe->max_logical_extent.height;
309 pcg 1.22 font_ascent = -xfe->max_logical_extent.y;
310 pcg 1.11 }
311    
312     if (geom_mask & XNegative)
313     win_x = win_x + ScreenWidth - width;
314     if (geom_mask & YNegative)
315     win_y = win_y + ScreenHeight - height;
316    
317 chris_moore 1.27 if (opt_outline)
318     {
319     /* adding outline increases the total width and height by 2
320     pixels each, and offsets the text one pixel right and one
321     pixel down */
322     effect_x_space = effect_y_space = 2;
323     effect_x_offset = effect_y_offset = 1;
324     }
325     else if (opt_shade)
326     {
327     /* adding a shadow increases the space used */
328     effect_x_space = abs(SHADE_X);
329     effect_y_space = abs(SHADE_Y);
330     /* if the shadow is to the right and below then we don't need
331     * to move the text to make space for it, but shadows to the left
332     * and above need accomodating */
333     effect_x_offset = SHADE_X > 0 ? 0 : -SHADE_X;
334     effect_y_offset = SHADE_Y > 0 ? 0 : -SHADE_Y;
335     }
336     else
337     {
338     effect_x_space = effect_y_space = 0;
339     effect_x_offset = effect_y_offset = 0;
340     }
341    
342     /* if we are using -shade or -outline, there will be less usable
343     * space for output */
344     listlen = (height - effect_y_space) / font_height;
345 pcg 1.1
346 pcg 1.11 if (!listlen)
347     {
348     fprintf (stderr, "height too small for a single line, setting to %d\n",
349     font_height);
350     listlen = 1;
351     }
352    
353 chris_moore 1.27 /* leave the height how the user requested it. it might not all be
354     * used, but this will allow the geometry to be tuned more accurately
355     * (with the -frame option)
356     * the old code did this:
357     * height = listlen * font_height + effect_y_space; */
358    
359 pcg 1.11 XSelectInput (disp, root, ExposureMask | FocusChangeMask);
360 pcg 1.1 }
361    
362     /*
363 chris_moore 1.28 * if redraw() is passwd a non-zero argument, it does a complete
364     * redraw, rather than an update. if the argument is zero (and
365     * -noflicker is in effect) then only the lines which have changed
366     * since the last draw are redrawn.
367     *
368 pcg 1.1 * the rest is handled by regular refresh()'es
369     */
370 pcg 1.11 void
371 chris_moore 1.28 redraw (int redraw_all)
372 pcg 1.1 {
373 pcg 1.20 XSetClipMask (disp, WinGC, None);
374 chris_moore 1.28 refresh (0, 32768, 1, redraw_all);
375 pcg 1.1 }
376    
377     /* Just redraw everything without clearing (i.e. after an EXPOSE event) */
378 pcg 1.11 void
379 chris_moore 1.28 refresh (int miny, int maxy, int clear, int refresh_all)
380 pcg 1.1 {
381 pcg 1.11 int lin;
382     int offset = (listlen + 1) * font_height;
383     unsigned long black_color = GetColor ("black");
384 pcg 1.1
385 pcg 1.11 miny -= win_y + font_height;
386     maxy -= win_y - font_height;
387 pcg 1.1
388 pcg 1.22 if (clear && !opt_noflicker)
389     XClearArea (disp, root, win_x, win_y, width, height, False);
390    
391 pcg 1.11 for (lin = listlen; lin--;)
392     {
393 pcg 1.12 struct linematrix *line = lines + (opt_reverse ? listlen - lin - 1 : lin);
394 chris_moore 1.28 struct displaymatrix *display_line = display + lin;
395 pcg 1.7
396 pcg 1.11 offset -= font_height;
397 pcg 1.7
398 pcg 1.11 if (offset < miny || offset > maxy)
399     continue;
400 jebusbfb 1.4
401 chris_moore 1.28 if (opt_noflicker)
402     {
403     /* if this line is a different than it was, then it
404     * needs displaying */
405     if (refresh_all ||
406     display_line->len != line->len ||
407     strcmp(display_line->line, line->line))
408     {
409     /* update the record of what has been displayed;
410     * first make sure the buffer is big enough */
411     if (display_line->buffer_size <= line->len)
412     {
413     display_line->buffer_size = line->len + 1;
414     display_line->line = xrealloc(display_line->line, display_line->buffer_size);
415     }
416     strcpy(display_line->line, line->line);
417     display_line->len = line->len;
418    
419     if (clear)
420     XClearArea (disp, root, win_x, win_y + offset - font_height,
421     width + effect_x_space, font_height + effect_y_space, False);
422 pcg 1.22
423 chris_moore 1.28 if (opt_outline)
424     {
425     int x, y;
426     XSetForeground (disp, WinGC, black_color);
427    
428     for (x = -1; x < 2; x += 2)
429     for (y = -1; y < 2; y += 2)
430     XmbDrawString (disp, root, fontset, WinGC,
431     win_x + effect_x_offset + x,
432     win_y + effect_y_offset + y + offset - font_height + font_ascent,
433     line->line, line->len);
434     }
435     else if (opt_shade)
436     {
437     XSetForeground (disp, WinGC, black_color);
438     XmbDrawString (disp, root, fontset, WinGC,
439     win_x + effect_x_offset + SHADE_X,
440     win_y + effect_y_offset + offset + SHADE_Y - font_height + font_ascent,
441     line->line, line->len);
442     }
443 chris_moore 1.27
444 chris_moore 1.28 XSetForeground (disp, WinGC, line->color);
445 chris_moore 1.27 XmbDrawString (disp, root, fontset, WinGC,
446 chris_moore 1.28 win_x + effect_x_offset,
447     win_y + effect_y_offset + offset - font_height + font_ascent,
448 chris_moore 1.27 line->line, line->len);
449 chris_moore 1.28 }
450     }
451     else
452     {
453     XSetForeground (disp, WinGC, line->color);
454     XmbDrawString (disp, root, fontset, WinGC,
455     win_x + effect_x_offset,
456     win_y + effect_y_offset + offset - font_height + font_ascent,
457 chris_moore 1.27 line->line, line->len);
458 chris_moore 1.28 }
459 pcg 1.1 }
460    
461 pcg 1.11 if (opt_frame)
462 pcg 1.15 {
463     XSetForeground (disp, WinGC, GetColor (def_color));
464 chris_moore 1.27 XDrawRectangle (disp, root, WinGC, win_x - 0, win_y - 0, width - 1, height - 1);
465 pcg 1.15 }
466 pcg 1.1 }
467    
468     #if HAS_REGEX
469 pcg 1.11 void
470     transform_line (char *s)
471 pcg 1.1 {
472     #ifdef I_AM_Md
473 pcg 1.11 int i;
474     if (1)
475     {
476     for (i = 16; s[i]; i++)
477     s[i] = s[i + 11];
478 pcg 1.1 }
479 pcg 1.11 s[i + 1] = '\0';
480 pcg 1.1 #endif
481    
482 pcg 1.11 if (transformre)
483     {
484     int i;
485     regmatch_t matched[16];
486    
487 chris_moore 1.26 i = regexec (transformre, s, 16, matched, 0);
488 pcg 1.11 if (i == 0)
489     { /* matched */
490 chris_moore 1.26 int match_start = matched[0].rm_so;
491     int match_end = matched[0].rm_eo;
492     int old_len = match_end - match_start;
493     int new_len = strlen(transform_to);
494     int old_whole_len = strlen(s);
495     printf("regexp was matched by '%s' - replace with '%s'\n", s, transform_to);
496     printf("match is from %d to %d\n",
497     match_start, match_end);
498     if (new_len > old_len) {
499 chris_moore 1.28 s = xrealloc(s, old_whole_len + new_len - old_len);
500 chris_moore 1.26 }
501     if (new_len != old_len) {
502     memcpy(s + match_end + new_len - old_len,
503     s + match_end,
504     old_whole_len - match_end);
505     s[old_whole_len + new_len - old_len] = '\0';
506     }
507     memcpy(s + match_start,
508     transform_to,
509     new_len);
510     printf("transformed to '%s'\n", s);
511 pcg 1.11 }
512 chris_moore 1.26 else
513     {
514     printf("regexp was not matched by '%s'\n", s);
515     }
516 pcg 1.1 }
517     }
518     #endif
519    
520 pcg 1.12 char *
521     concat_line (const char *p1, const char *p2)
522     {
523     int l1 = p1 ? strlen (p1) : 0;
524     int l2 = strlen (p2);
525     char *r = xmalloc (l1 + l2 + 1);
526    
527     memcpy (r, p1, l1);
528     memcpy (r + l1, p2, l2);
529     r[l1 + l2] = 0;
530    
531     return r;
532     }
533 pcg 1.1
534     /*
535 pcg 1.12 * This routine should read a single line, no matter how long.
536 pcg 1.1 */
537 pcg 1.11 int
538     lineinput (struct logfile_entry *logfile)
539 pcg 1.1 {
540 pcg 1.12 char buff[1024], *p = buff;
541     int ch;
542 chris_moore 1.26 /* HACK this - to add on the length of any partial line which we will be appending to */
543 pcg 1.12 int ofs = logfile->buf ? strlen (logfile->buf) : 0;
544 pcg 1.1
545 pcg 1.11 do
546     {
547 pcg 1.12 ch = fgetc (logfile->fp);
548 pcg 1.1
549 pcg 1.18 if (ch == '\n' || ch == EOF)
550     break;
551 pcg 1.17 else if (ch == '\r')
552 pcg 1.18 continue; /* skip */
553 pcg 1.12 else if (ch == '\t')
554     {
555     do
556     {
557     *p++ = ' ';
558     ofs++;
559     }
560     while (ofs & 7);
561     }
562     else
563     {
564     *p++ = ch;
565     ofs++;
566     }
567 pcg 1.11 }
568 pcg 1.12 while (p < buff + (sizeof buff) - 8 - 1);
569 pcg 1.1
570 pcg 1.19 if (p == buff && ch == EOF)
571 pcg 1.12 return 0;
572    
573     *p = 0;
574    
575     p = concat_line (logfile->buf, buff);
576     free (logfile->buf); logfile->buf = p;
577 chris_moore 1.9
578 pcg 1.12 logfile->lastpartial = logfile->partial;
579 chris_moore 1.25 /* there are 3 ways we could have exited the loop: reading '\n',
580     * reaching EOF, or filling the buffer; the 2nd and 3rd of these
581     * both result in a partial line */
582     logfile->partial = ch != '\n';
583 pcg 1.12
584     if (logfile->partial && opt_whole)
585 pcg 1.11 return 0;
586 pcg 1.1
587     #if HAS_REGEX
588 pcg 1.12 transform_line (logfile->buf);
589 pcg 1.1 #endif
590 pcg 1.12 return 1;
591 pcg 1.1 }
592    
593     /* input: reads file->fname
594     * output: fills file->fp, file->inode
595     * returns file->fp
596     * in case of error, file->fp is NULL
597     */
598 pcg 1.11 FILE *
599     openlog (struct logfile_entry * file)
600     {
601     struct stat stats;
602    
603     if ((file->fp = fopen (file->fname, "r")) == NULL)
604     {
605     file->fp = NULL;
606     return NULL;
607     }
608    
609     fstat (fileno (file->fp), &stats);
610     if (S_ISFIFO (stats.st_mode))
611     {
612     if (fcntl (fileno (file->fp), F_SETFL, O_NONBLOCK) < 0)
613     perror ("fcntl"), exit (1);
614     file->inode = 0;
615     }
616     else
617     file->inode = stats.st_ino;
618    
619     if (opt_noinitial)
620     fseek (file->fp, 0, SEEK_END);
621     else if (stats.st_size > (listlen + 1) * width)
622     fseek (file->fp, -((listlen + 2) * width), SEEK_END);
623    
624     file->last_size = stats.st_size;
625     return file->fp;
626     }
627    
628     void
629     reopen (void)
630 pcg 1.1 {
631 pcg 1.11 struct logfile_entry *e;
632    
633     for (e = loglist; e; e = e->next)
634     {
635     if (!e->inode)
636     continue; /* skip stdin */
637 pcg 1.1
638 pcg 1.11 if (e->fp)
639     fclose (e->fp);
640     /* if fp is NULL we will try again later */
641     openlog (e);
642     }
643    
644     do_reopen = 0;
645     }
646 pcg 1.1
647 pcg 1.11 void
648     check_open_files (void)
649     {
650     struct logfile_entry *e;
651     struct stat stats;
652 pcg 1.1
653 pcg 1.11 for (e = loglist; e; e = e->next)
654     {
655     if (!e->inode)
656     continue; /* skip stdin */
657 pcg 1.1
658 pcg 1.11 if (stat (e->fname, &stats) < 0)
659     { /* file missing? */
660     sleep (1);
661     if (e->fp)
662     fclose (e->fp);
663     if (openlog (e) == NULL)
664 chris_moore 1.23 continue;
665 pcg 1.11 }
666    
667 chris_moore 1.23 /* HACK this - stats can be uninitialised here (if the file
668     * didn't exist when stat() was called, but was recreated during
669     * the sleep(1)) */
670 pcg 1.11 if (stats.st_ino != e->inode)
671     { /* file renamed? */
672     if (e->fp)
673     fclose (e->fp);
674     if (openlog (e) == NULL)
675 chris_moore 1.23 continue;
676 pcg 1.11 }
677    
678     if (stats.st_size < e->last_size)
679     { /* file truncated? */
680     fseek (e->fp, 0, SEEK_SET);
681     e->last_size = stats.st_size;
682     }
683 pcg 1.1 }
684     }
685    
686 pcg 1.14 /*
687     * insert a single physical line (that must be short enough to fit)
688     * at position "idx" by pushing up lines above it. the caller
689     * MUST then fill in lines[idx] with valid data.
690     */
691 pcg 1.12 static void
692     insert_line (int idx)
693     {
694     int cur_line;
695     struct logfile_entry *current;
696    
697     free (lines[0].line);
698    
699     for (cur_line = 0; cur_line < idx; cur_line++)
700     lines[cur_line] = lines[cur_line + 1];
701    
702     for (current = loglist; current; current = current->next)
703 pcg 1.16 if (current->index <= idx)
704 pcg 1.12 current->index--;
705 pcg 1.11 }
706    
707 pcg 1.14 /*
708     * remove a single physical line at position "idx" by moving the lines above it
709     * down and inserting a "~" line at the top.
710     */
711 pcg 1.12 static void
712     delete_line (int idx)
713     {
714     int cur_line;
715     struct logfile_entry *current;
716    
717     for (cur_line = idx; cur_line > 0; cur_line--)
718     lines[cur_line] = lines[cur_line - 1];
719    
720 chris_moore 1.28 lines[0].line = xstrdup ("~");
721 pcg 1.12
722     for (current = loglist; current; current = current->next)
723 pcg 1.16 if (current->index >= 0 && current->index <= idx)
724 pcg 1.12 current->index++;
725     }
726    
727 pcg 1.14 /*
728 chris_moore 1.24 * takes a logical log file line and splits it into multiple physical
729 pcg 1.14 * screen lines by splitting it whenever a part becomes too long.
730     * lal lines will be inserted at position "idx".
731     */
732 pcg 1.12 static void
733     split_line (int idx, const char *str, unsigned long color)
734     {
735     int l = strlen (str);
736     const char *p = str;
737    
738     do
739     {
740     const char *beg = p;
741 chris_moore 1.24 int w = 0, wrapped = 0;
742     const char *break_p = NULL;
743 pcg 1.12
744     while (*p)
745     {
746 chris_moore 1.24 /* find the length in bytes of the next multibyte character */
747 pcg 1.12 int len = mblen (p, l);
748     if (len <= 0)
749 chris_moore 1.24 len = 1; /* ignore (don't skip) illegal character sequences */
750 pcg 1.12
751 chris_moore 1.24 /* find the width in pixels of the next character */
752 pcg 1.12 int cw = XmbTextEscapement (fontset, p, len);
753 chris_moore 1.27 if (cw + w > width - effect_x_space)
754 chris_moore 1.24 {
755     wrapped = 1;
756     break;
757     }
758    
759     if (opt_wordwrap && len == 1 && p[0] == ' ')
760     break_p = p;
761 pcg 1.12
762     w += cw;
763     p += len;
764     l -= len;
765     }
766    
767 chris_moore 1.24 /* if we're wrapping at spaces, and the line is long enough to
768     * wrap, and we've seen a space already, and the space wasn't
769     * the first character on the line, then wrap at the space */
770     if (opt_wordwrap && wrapped && break_p && break_p != beg)
771     {
772     l += p - break_p;
773     p = break_p;
774     }
775    
776 pcg 1.12 {
777     char *s = xmalloc (p - beg + 1);
778     memcpy (s, beg, p - beg);
779     s[p - beg] = 0;
780     insert_line (idx);
781     lines[idx].line = s;
782     lines[idx].len = p - beg;
783     lines[idx].color = color;
784     }
785 chris_moore 1.24
786     /* if we wrapped at a space, don't display the space */
787     if (opt_wordwrap && wrapped && break_p && break_p != beg)
788     {
789     l--;
790     p++;
791     }
792 pcg 1.12 }
793     while (l);
794     }
795    
796 pcg 1.14 /*
797     * append something to an existing physical line. this is done
798     * by deleting the file on-screen, concatenating the new data to it
799     * and splitting it again.
800     */
801 pcg 1.12 static void
802     append_line (int idx, const char *str)
803     {
804     unsigned long color = lines[idx].color;
805     char *old = lines[idx].line;
806     char *new = concat_line (old, str);
807    
808     free (old);
809    
810     delete_line (idx);
811     split_line (idx, new, color);
812     }
813    
814     static void
815 pcg 1.11 main_loop (void)
816     {
817 pcg 1.12 lines = xmalloc (sizeof (struct linematrix) * listlen);
818 chris_moore 1.28 display = xmalloc (sizeof (struct displaymatrix) * listlen);
819 pcg 1.20 int lin;
820 pcg 1.11 time_t lastreload;
821     Region region = XCreateRegion ();
822     XEvent xev;
823     struct logfile_entry *lastprinted = NULL;
824     struct logfile_entry *current;
825 pcg 1.18 int need_update = 1;
826 pcg 1.11
827     lastreload = time (NULL);
828    
829     /* Initialize linematrix */
830     for (lin = 0; lin < listlen; lin++)
831     {
832 chris_moore 1.28 lines[lin].line = xstrdup ("~");
833 pcg 1.12 lines[lin].len = 1;
834 chris_moore 1.28 display[lin].line = xstrdup("");
835     display[lin].len = 0;
836     display[lin].buffer_size = 1;
837 pcg 1.11 lines[lin].color = GetColor (def_color);
838     }
839    
840     for (;;)
841     {
842     /* read logs */
843     for (current = loglist; current; current = current->next)
844     {
845     if (!current->fp)
846     continue; /* skip missing files */
847    
848     clearerr (current->fp);
849    
850 pcg 1.12 while (lineinput (current))
851 pcg 1.11 {
852 pcg 1.12 need_update = 1;
853 pcg 1.11 /* if we're trying to update old partial lines in
854     * place, and the last time this file was updated the
855     * output was partial, and that partial line is not
856     * too close to the top of the screen, then update
857     * that partial line */
858 pcg 1.16 if (opt_update && current->lastpartial && current->index >= 0)
859 pcg 1.11 {
860 pcg 1.16 int idx = current->index;
861     append_line (idx, current->buf);
862     current->index = idx;
863 pcg 1.13 free (current->buf), current->buf = 0;
864 pcg 1.12 continue;
865 pcg 1.11 }
866    
867     /* print filename if any, and if last line was from
868     * different file */
869 pcg 1.13 if (!opt_nofilename && lastprinted != current && current->desc[0])
870 pcg 1.11 {
871 pcg 1.12 char buf[1024]; /* HACK */
872     snprintf (buf, sizeof (buf), "[%s]", current->desc);
873     split_line (listlen - 1, buf, current->color);
874 pcg 1.11 }
875    
876     /* if this is the same file we showed last, and the
877     * last time we showed it, it wasn't finished, then
878     * append to the last line shown */
879     if (lastprinted == current && current->lastpartial)
880 chris_moore 1.23 append_line (listlen - 1, current->buf);
881     else if (current->lastpartial)
882     {
883     split_line (listlen - 1, continuation, current->color);
884 pcg 1.12 append_line (listlen - 1, current->buf);
885 chris_moore 1.23 }
886 pcg 1.11 else
887 chris_moore 1.23 split_line (listlen - 1, current->buf, current->color);
888 pcg 1.11
889 chris_moore 1.23 free (current->buf), current->buf = 0;
890 pcg 1.11 current->index = listlen - 1;
891     lastprinted = current;
892     }
893     }
894    
895     if (need_update)
896 pcg 1.18 {
897 chris_moore 1.28 redraw (0);
898 pcg 1.18 need_update = 0;
899     }
900 pcg 1.11 else
901     {
902     XFlush (disp);
903    
904     if (!XPending (disp))
905     {
906     fd_set fdr;
907     struct timeval to = interval;
908    
909     FD_ZERO (&fdr);
910     FD_SET (ConnectionNumber (disp), &fdr);
911     select (ConnectionNumber (disp) + 1, &fdr, 0, 0, &to);
912     }
913     }
914    
915     check_open_files ();
916    
917     if (do_reopen)
918     reopen ();
919    
920     /* we ignore possible errors due to window resizing &c */
921     while (XPending (disp))
922     {
923     XNextEvent (disp, &xev);
924    
925     switch (xev.type)
926     {
927     case Expose:
928     {
929     XRectangle r;
930    
931     r.x = xev.xexpose.x;
932     r.y = xev.xexpose.y;
933     r.width = xev.xexpose.width;
934     r.height = xev.xexpose.height;
935 pcg 1.20
936 pcg 1.11 XUnionRectWithRegion (&r, region, region);
937     }
938     break;
939     default:
940     #ifdef DEBUGMODE
941     fprintf (stderr, "PANIC! Unknown event %d\n", xev.type);
942     #endif
943     break;
944     }
945     }
946    
947     /* reload if requested */
948     if (reload && lastreload + reload < time (NULL))
949     {
950     if (command && command[0])
951     system (command);
952    
953     reopen ();
954     lastreload = time (NULL);
955     }
956    
957     if (!XEmptyRegion (region))
958     {
959 pcg 1.20 XRectangle r;
960    
961 pcg 1.11 XSetRegion (disp, WinGC, region);
962 pcg 1.20 XClipBox (region, &r);
963    
964 chris_moore 1.28 refresh (r.y, r.y + r.height, 0, 1);
965 pcg 1.11
966     XDestroyRegion (region);
967     region = XCreateRegion ();
968     }
969     }
970     }
971 pcg 1.1
972 pcg 1.11
973     int
974     main (int argc, char *argv[])
975 pcg 1.1 {
976 pcg 1.11 int i;
977     int opt_daemonize = 0;
978     int opt_partial = 0, file_count = 0;
979 pcg 1.1 #if HAS_REGEX
980 pcg 1.11 char *transform = NULL;
981 pcg 1.1 #endif
982    
983 pcg 1.11 setlocale (LC_CTYPE, ""); /* try to initialize the locale. */
984    
985     /* window needs to be initialized before colorlookups can be done */
986     /* just a dummy to get the color lookups right */
987     geom_mask = NoValue;
988     InitWindow ();
989    
990     for (i = 1; i < argc; i++)
991     {
992     const char *arg = argv[i];
993 pcg 1.7
994 pcg 1.11 if (arg[0] == '-' && arg[1] != '\0' && arg[1] != ',')
995     {
996     if (arg[1] == '-')
997     arg++;
998    
999     if (!strcmp (arg, "-?") ||
1000     !strcmp (arg, "-help") || !strcmp (arg, "-h"))
1001     display_help (argv[0]);
1002     else if (!strcmp (arg, "-V"))
1003     display_version ();
1004     else if (!strcmp (arg, "-g") || !strcmp (arg, "-geometry"))
1005     geom_mask =
1006     XParseGeometry (argv[++i], &win_x, &win_y, &width, &height);
1007     else if (!strcmp (arg, "-display"))
1008     dispname = argv[++i];
1009     else if (!strcmp (arg, "-cont"))
1010     continuation = argv[++i];
1011     else if (!strcmp (arg, "-font") || !strcmp (arg, "-fn"))
1012     fontname = argv[++i];
1013 pcg 1.1 #if HAS_REGEX
1014 pcg 1.11 else if (!strcmp (arg, "-t"))
1015 chris_moore 1.26 {
1016     transform = argv[++i];
1017     transform_to = argv[++i];
1018     printf("transform: '%s' to '%s'\n", transform, transform_to);
1019     }
1020 pcg 1.1 #endif
1021 pcg 1.11 else if (!strcmp (arg, "-fork") || !strcmp (arg, "-f"))
1022     opt_daemonize = 1;
1023     else if (!strcmp (arg, "-reload"))
1024     {
1025     reload = atoi (argv[++i]);
1026     command = argv[++i];
1027     }
1028     else if (!strcmp (arg, "-shade"))
1029     opt_shade = 1;
1030 pcg 1.21 else if (!strcmp (arg, "-outline"))
1031     opt_outline = 1;
1032 pcg 1.22 else if (!strcmp (arg, "-noflicker"))
1033     opt_noflicker = 1;
1034 pcg 1.11 else if (!strcmp (arg, "-frame"))
1035     opt_frame = 1;
1036     else if (!strcmp (arg, "-no-filename"))
1037     opt_nofilename = 1;
1038     else if (!strcmp (arg, "-reverse"))
1039     opt_reverse = 1;
1040     else if (!strcmp (arg, "-whole"))
1041     opt_whole = 1;
1042     else if (!strcmp (arg, "-partial"))
1043     opt_partial = 1;
1044     else if (!strcmp (arg, "-update"))
1045     opt_update = opt_partial = 1;
1046 chris_moore 1.24 else if (!strcmp (arg, "-wordwrap"))
1047     opt_wordwrap = 1;
1048 pcg 1.11 else if (!strcmp (arg, "-color"))
1049     def_color = argv[++i];
1050     else if (!strcmp (arg, "-noinitial"))
1051     opt_noinitial = 1;
1052     else if (!strcmp (arg, "-id"))
1053     root = atoi (argv[++i]);
1054     else if (!strcmp (arg, "-interval") || !strcmp (arg, "-i"))
1055     {
1056     double iv = atof (argv[++i]);
1057    
1058     interval.tv_sec = (int) iv;
1059     interval.tv_usec = (iv - interval.tv_sec) * 1e6;
1060     }
1061     else
1062     {
1063     fprintf (stderr, "Unknown option '%s'.\n"
1064     "Try --help for more information.\n", arg);
1065     exit (1);
1066     }
1067     }
1068     else
1069     { /* it must be a filename */
1070     struct logfile_entry *e;
1071     const char *fname, *desc, *fcolor = def_color;
1072     char *p;
1073    
1074     file_count++;
1075    
1076     /* this is not foolproof yet (',' in filenames are not allowed) */
1077     fname = desc = arg;
1078     if ((p = strchr (arg, ',')))
1079     {
1080     *p = '\0';
1081     fcolor = p + 1;
1082    
1083     if ((p = strchr (fcolor, ',')))
1084     {
1085     *p = '\0';
1086     desc = p + 1;
1087     }
1088     }
1089    
1090     e = xmalloc (sizeof (struct logfile_entry));
1091 pcg 1.12 e->partial = 0;
1092     e->buf = 0;
1093 pcg 1.16 e->index = -1;
1094 pcg 1.12
1095 pcg 1.11 if (arg[0] == '-' && arg[1] == '\0')
1096     {
1097     if ((e->fp = fdopen (0, "r")) == NULL)
1098     perror ("fdopen"), exit (1);
1099     if (fcntl (0, F_SETFL, O_NONBLOCK) < 0)
1100     perror ("fcntl"), exit (1);
1101     e->fname = NULL;
1102     e->inode = 0;
1103     e->desc = xstrdup ("stdin");
1104     }
1105     else
1106     {
1107     int l;
1108    
1109     e->fname = xstrdup (fname);
1110     if (openlog (e) == NULL)
1111     perror (fname), exit (1);
1112    
1113     l = strlen (desc);
1114 chris_moore 1.27 /* HACK on this - width is in pixels now */
1115 pcg 1.11 if (l > width - 2) /* must account for [ ] */
1116     l = width - 2;
1117     e->desc = xmalloc (l + 1);
1118     memcpy (e->desc, desc, l);
1119     *(e->desc + l) = '\0';
1120     }
1121    
1122     e->color = GetColor (fcolor);
1123     e->partial = 0;
1124     e->next = NULL;
1125    
1126     if (!loglist)
1127     loglist = e;
1128     if (loglist_tail)
1129     loglist_tail->next = e;
1130     loglist_tail = e;
1131     }
1132     }
1133    
1134     if (!loglist)
1135     {
1136     fprintf (stderr, "You did not specify any files to tail\n"
1137     "use %s --help for help\n", argv[0]);
1138     exit (1);
1139     }
1140    
1141     if (opt_partial && opt_whole)
1142     {
1143     fprintf (stderr, "Specify at most one of -partial and -whole\n");
1144     exit (1);
1145 chris_moore 1.9 }
1146    
1147 chris_moore 1.27 /* HACK this - do we want to allow both -shade and -outline? */
1148     if (opt_shade && opt_outline)
1149     {
1150     fprintf (stderr, "Specify at most one of -shade and -outline\n");
1151     exit (1);
1152     }
1153    
1154 pcg 1.11 if (opt_partial)
1155 chris_moore 1.9 /* if we specifically requested to see partial lines then don't insist on whole lines */
1156 pcg 1.11 opt_whole = 0;
1157     else if (file_count > 1)
1158 chris_moore 1.9 /* otherwise, if we've viewing multiple files, default to showing whole lines */
1159 pcg 1.11 opt_whole = 1;
1160 chris_moore 1.9
1161 pcg 1.1 #if HAS_REGEX
1162 pcg 1.11 if (transform)
1163     {
1164     int i;
1165 pcg 1.1
1166 chris_moore 1.26 printf("compiling regexp '%s'\n", transform);
1167     transformre = xmalloc (sizeof (regex_t));
1168     i = regcomp (transformre, transform, REG_EXTENDED);
1169 pcg 1.11 if (i != 0)
1170     {
1171     char buf[512];
1172    
1173 chris_moore 1.26 regerror (i, transformre, buf, sizeof (buf));
1174 pcg 1.11 fprintf (stderr, "Cannot compile regular expression: %s\n", buf);
1175     }
1176 chris_moore 1.26 else
1177     {
1178     printf("compiled '%s' OK to %x\n", transform, (int)transformre);
1179     }
1180 pcg 1.1 }
1181     #endif
1182    
1183 pcg 1.11 InitWindow ();
1184 pcg 1.1
1185 pcg 1.11 install_signal (SIGINT, blank_window);
1186     install_signal (SIGQUIT, blank_window);
1187     install_signal (SIGTERM, blank_window);
1188     install_signal (SIGHUP, force_reopen);
1189     install_signal (SIGUSR1, list_files);
1190     install_signal (SIGUSR2, force_refresh);
1191 pcg 1.1
1192 pcg 1.11 if (opt_daemonize)
1193     daemonize ();
1194 pcg 1.1
1195 pcg 1.11 main_loop ();
1196 pcg 1.1
1197 pcg 1.11 exit (1); /* to make gcc -Wall stop complaining */
1198 pcg 1.1 }
1199    
1200 pcg 1.11 void
1201     install_signal (int sig, void (*handler) (int))
1202 pcg 1.1 {
1203 pcg 1.11 struct sigaction action;
1204    
1205     action.sa_handler = handler;
1206     sigemptyset (&action.sa_mask);
1207     action.sa_flags = SA_RESTART;
1208 pcg 1.1
1209 pcg 1.11 if (sigaction (sig, &action, NULL) < 0)
1210     fprintf (stderr, "sigaction(%d): %s\n", sig, strerror (errno)), exit (1);
1211 pcg 1.1 }
1212    
1213 pcg 1.11 void *
1214     xstrdup (const char *string)
1215 pcg 1.1 {
1216 pcg 1.11 void *p;
1217 pcg 1.1
1218 pcg 1.11 while ((p = strdup (string)) == NULL)
1219     {
1220     fprintf (stderr, "Memory exausted.");
1221     sleep (10);
1222 pcg 1.1 }
1223 pcg 1.11
1224     return p;
1225 pcg 1.1 }
1226    
1227 pcg 1.11 void *
1228     xmalloc (size_t size)
1229 pcg 1.1 {
1230 pcg 1.11 void *p;
1231 pcg 1.1
1232 pcg 1.11 while ((p = malloc (size)) == NULL)
1233     {
1234     fprintf (stderr, "Memory exausted.");
1235     sleep (10);
1236 pcg 1.1 }
1237 pcg 1.12
1238 pcg 1.11 return p;
1239 pcg 1.1 }
1240    
1241 chris_moore 1.28 void *
1242     xrealloc (void *ptr, size_t size)
1243     {
1244     void *p;
1245    
1246     while ((p = realloc (ptr, size)) == NULL)
1247     {
1248     fprintf (stderr, "Memory exausted.");
1249     sleep (10);
1250     }
1251    
1252     return p;
1253     }
1254    
1255 pcg 1.11 void
1256     display_help (char *myname)
1257     {
1258     printf ("Usage: %s [options] file1[,color[,desc]] "
1259     "[file2[,color[,desc]] ...]\n", myname);
1260     printf (" -g | -geometry geometry -g WIDTHxHEIGHT+X+Y\n"
1261     " -color color use color $color as default\n"
1262     " -reload sec command reload after $sec and run command\n"
1263     " -id id window id to use instead of the root window\n"
1264     " -font FONTSPEC (-fn) font to use\n"
1265     " -f | -fork fork into background\n"
1266     " -reverse print new lines at the top\n"
1267     " -whole wait for \\n before showing a line\n"
1268     " -partial show lines even if they don't end with a \\n\n"
1269     " -update allow updates to old partial lines\n"
1270     " -cont string to prefix continued partial lines with\n"
1271 chris_moore 1.24 " -wordwrap wrap long lines at spaces to avoid breaking words\n"
1272 pcg 1.11 " defaults to \"[+]\"\n"
1273     " -shade add shading to font\n"
1274     " -noinitial don't display the last file lines on\n"
1275     " startup\n"
1276     " -i | -interval seconds interval between checks (fractional\n"
1277 pcg 1.21 " values o.k.). Default 2.4 seconds\n"
1278 pcg 1.11 " -V display version information and exit\n"
1279     "\n");
1280     printf ("Example:\n%s -g 80x25+100+50 -font fixed /var/log/messages,green "
1281     "/var/log/secure,red,'ALERT'\n", myname);
1282     exit (0);
1283     }
1284    
1285     void
1286     display_version (void)
1287 pcg 1.1 {
1288 pcg 1.11 printf ("root-tail version " VERSION "\n");
1289     exit (0);
1290 pcg 1.1 }
1291    
1292 pcg 1.11 int
1293     daemonize (void)
1294     {
1295 chris_moore 1.26 pid_t pid;
1296    
1297     switch (pid = fork ())
1298 pcg 1.11 {
1299 pcg 1.1 case -1:
1300 pcg 1.11 return -1;
1301 pcg 1.1 case 0:
1302 pcg 1.11 break;
1303 pcg 1.1 default:
1304 chris_moore 1.26 printf("%d\n", pid);
1305     exit (0);
1306 pcg 1.1 }
1307    
1308 pcg 1.11 if (setsid () == -1)
1309     return -1;
1310 pcg 1.1
1311 pcg 1.11 return 0;
1312 pcg 1.1 }