ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/root-tail/root-tail.c
Revision: 1.66
Committed: Sun Jun 16 03:25:52 2019 UTC (4 years, 10 months ago) by root
Content type: text/plain
Branch: MAIN
CVS Tags: HEAD
Changes since 1.65: +0 -12 lines
Log Message:
*** empty log message ***

File Contents

# Content
1 /*
2 * Copyright 2001 by Marco d'Itri <md@linux.it>
3 * Copyright 2000,2001,2002,2003,2004
4 * Marc Lehmann <pcg@goof.com>,
5 * Chris Moore <chris.moore@mail.com>,
6 * and many others, see README
7 *
8 * Original version by Mike Baker.
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
23 */
24
25 #include "config.h"
26 #include <assert.h>
27 #include <stdlib.h>
28 #include <stdio.h>
29 #include <unistd.h>
30 #include <string.h>
31 #include <signal.h>
32 #include <time.h>
33 #include <fcntl.h>
34 #include <errno.h>
35 #include <sys/time.h>
36 #include <sys/stat.h>
37 #include <sys/types.h>
38 #include <locale.h>
39 #include <ctype.h>
40 #include <stdarg.h>
41 #include <X11/Xlib.h>
42 #include <X11/Xatom.h>
43 #include <X11/Xutil.h>
44 #include <X11/extensions/shape.h>
45 #include <X11/extensions/Xfixes.h>
46
47 #if HAS_REGEX
48 #include <regex.h>
49 #endif
50
51 #define SHADE_X 2
52 #define SHADE_Y 2
53
54 /* some italic fonts still go over the margin - this margin of error cleans up the mess */
55 #define MARGIN_OF_ERROR 2
56
57 /* data structures */
58 struct logfile_entry
59 {
60 struct logfile_entry *next;
61
62 char *fname; /* name of file */
63 char *desc; /* alternative description */
64 char *buf; /* text read but not yet displayed */
65 const char *fontname;
66 XFontSet fontset;
67 int font_height;
68 int font_ascent;
69 FILE *fp; /* FILE struct associated with file */
70 ino_t inode; /* inode of the file opened */
71 off_t last_size; /* file size at the last check */
72 unsigned long color; /* color to be used for printing */
73 const char *colorname; /* color name/string */
74 int partial; /* true if the last line isn't complete */
75 int lastpartial; /* true if the previous output wasn't complete */
76 struct line_node *last; /* last line we output */
77 int modified; /* true if line is modified & needs displaying */
78 };
79
80 struct line_node
81 {
82 struct line_node *next;
83 struct line_node *prev;
84 struct logfile_entry *logfile;
85
86 char *line; /* the text of the line (so far) */
87 int len; /* the length of the line (in bytes) so far */
88 int wrapped_left; /* true if wrapped from the previous line */
89 int wrapped_right; /* true if wrapped to the next line */
90 struct breakinfo *breaks; /* array of indicies to spaces if wrapped_right */
91 int num_words; /* the number of words in the line */
92 int free_pixels; /* the number of free pixels to spread out */
93 };
94
95 struct breakinfo
96 {
97 int index; /* index into string of start of substring */
98 int width; /* width in pixels of start of substring */
99 int len; /* length of substring */
100 };
101
102 struct displaymatrix
103 {
104 char *line;
105 int len;
106 int offset;
107 int buffer_size;
108 unsigned long color;
109 };
110
111 /* global variables */
112 static struct line_node *linelist = NULL, *linelist_tail = NULL;
113 static struct displaymatrix *display;
114 static int continuation_width = -1;
115 static int continuation_color;
116 static int continuation_length;
117
118 /* HACK - ideally listlen will start at however many '~'s will fit on
119 * the screen */
120 static unsigned int width = STD_WIDTH, height = STD_HEIGHT;
121 static int listlen = 50;
122 static int win_x = LOC_X, win_y = LOC_Y;
123 static int effect_x_space, effect_y_space; /* how much space does shading / outlining take up */
124 static int effect_x_offset, effect_y_offset; /* and how does it offset the usable space */
125 static int do_reopen;
126 static struct timeval interval = { 2, 400000 };
127
128 /* command line options */
129 static int opt_noinitial, opt_shade, opt_frame, opt_reverse, opt_nofilename,
130 opt_outline, opt_noflicker, opt_whole, opt_update, opt_wordwrap,
131 opt_justify, geom_mask, opt_minspace, opt_windowed, reload;
132 static const char *command = NULL,
133 *fontname = USE_FONT, *dispname = NULL, *def_color = DEF_COLOR,
134 *continuation = "|| ", *cont_color = DEF_CONT_COLOR;
135
136 struct logfile_entry *loglist = NULL, *loglist_tail = NULL;
137
138 static Display *disp;
139 static Window root;
140 static GC WinGC;
141
142 #if HAS_REGEX
143 struct re_list
144 {
145 regex_t from;
146 const char *to;
147 struct re_list *next;
148 };
149 static struct re_list *re_head, *re_tail;
150 static char *transform_to = NULL;
151 static regex_t *transformre;
152 #endif
153
154
155 /* prototypes */
156 #ifdef USE_TOON_GET_ROOT_WINDOW
157 static Window ToonGetRootWindow(Display *, int, Window *);
158 #endif /* USE_TOON_GET_ROOT_WINDOW */
159
160 static void redraw (int);
161 static void refresh (int, int, int, int);
162
163 static void display_version (void);
164 static void display_help (char *);
165 static void install_signal (int, void (*)(int));
166 static void *xstrdup (const char *);
167 static void *xmalloc (size_t);
168 static void *xrealloc (void *, size_t);
169 static int daemonize (void);
170
171 /* signal handlers */
172 static void
173 list_files (int dummy)
174 {
175 struct logfile_entry *e;
176
177 fprintf (stderr, "Files opened:\n");
178 for (e = loglist; e; e = e->next)
179 fprintf (stderr, "\t%s (%s)\n", e->fname, e->desc);
180 }
181
182 static void
183 force_reopen (int dummy)
184 {
185 do_reopen = 1;
186 }
187
188 static void
189 force_refresh (int dummy)
190 {
191 redraw (1);
192 }
193
194 static void
195 blank_window (int dummy)
196 {
197 XClearArea (disp, root, win_x, win_y, width + MARGIN_OF_ERROR, height, False);
198 XFlush (disp);
199 exit (0);
200 }
201
202 /* X related functions */
203 static unsigned long
204 GetColor (const char *ColorName)
205 {
206 XColor Color;
207 XWindowAttributes Attributes;
208
209 XGetWindowAttributes (disp, root, &Attributes);
210 Color.pixel = 0;
211
212 if (!XParseColor (disp, Attributes.colormap, ColorName, &Color))
213 fprintf (stderr, "can't parse %s\n", ColorName);
214 else if (!XAllocColor (disp, Attributes.colormap, &Color))
215 fprintf (stderr, "can't allocate %s\n", ColorName);
216
217 return Color.pixel;
218 }
219
220 #ifndef USE_TOON_GET_ROOT_WINDOW
221 static void
222 find_root_window (Display *display, int screen_number)
223 {
224 if (!root)
225 {
226 Atom SWM_VROOT = XInternAtom (display, "__SWM_VROOT", False);
227 Atom NAUTILUS_DESKTOP_WINDOW_ID = XInternAtom (display, "NAUTILUS_DESKTOP_WINDOW_ID", False);
228
229 Window unused, *windows = 0;
230 unsigned int count;
231
232 Atom type;
233 int format;
234 unsigned long nitems, bytes_after_return;
235 unsigned char *virtual_root_window;
236
237 root = RootWindow (display, screen_number);
238
239 if (XGetWindowProperty (display, root, NAUTILUS_DESKTOP_WINDOW_ID,
240 0, 1, False, XA_WINDOW, &type, &format,
241 &nitems, &bytes_after_return,
242 &virtual_root_window) == Success
243 && type == XA_WINDOW)
244 {
245 if (XQueryTree (display, *(Window *)virtual_root_window, &unused, &unused, &windows, &count))
246 root = windows[count - 1];
247
248 XFree (virtual_root_window);
249 }
250 else if (XQueryTree (display, root, &unused, &unused, &windows, &count))
251 {
252 int i;
253
254 for (i = 0; i < count; i++)
255 {
256 if (XGetWindowProperty (display, windows[i], SWM_VROOT,
257 0, 1, False, XA_WINDOW, &type, &format,
258 &nitems, &bytes_after_return,
259 &virtual_root_window) == Success
260 && type == XA_WINDOW)
261 {
262 root = *(Window *)virtual_root_window;
263 XFree (virtual_root_window);
264 break;
265 }
266 }
267
268 XFree (windows);
269 }
270 else
271 fprintf (stderr, "Can't query tree on root window 0x%lx", root);
272 }
273 }
274 #endif /* USE_TOON_GET_ROOT_WINDOW */
275
276 static void
277 InitWindow (void)
278 {
279 XGCValues gcv;
280 unsigned long gcm;
281 int screen, ScreenWidth, ScreenHeight;
282 struct logfile_entry *e;
283
284 if (!(disp = XOpenDisplay (dispname)))
285 {
286 fprintf (stderr, "Can't open display %s.\n", dispname);
287 exit (1);
288 }
289
290 screen = DefaultScreen (disp);
291 ScreenHeight = DisplayHeight (disp, screen);
292 ScreenWidth = DisplayWidth (disp, screen);
293
294 if (opt_windowed)
295 {
296 XRectangle rect = { };
297 XSetWindowAttributes attr;
298
299 attr.background_pixmap = ParentRelative;
300 attr.override_redirect = True;
301
302 root = XCreateWindow (
303 disp, DefaultRootWindow (disp), 0, 0, DisplayWidth (disp, screen), DisplayHeight (disp, screen),
304 0, CopyFromParent, InputOutput, CopyFromParent,
305 CWOverrideRedirect | CWBackPixmap, &attr);
306
307 XMapWindow (disp, root);
308 XLowerWindow (disp, root);
309
310 XserverRegion region = XFixesCreateRegion (disp, &rect, 1);
311 XFixesSetWindowShapeRegion (disp, root, ShapeInput, 0, 0, region);
312 XFixesDestroyRegion (disp, region);
313 }
314 else
315 find_root_window (disp, screen);
316
317 gcm = GCBackground;
318 gcv.graphics_exposures = True;
319 WinGC = XCreateGC (disp, root, gcm, &gcv);
320 XMapWindow (disp, root);
321
322 XSetForeground (disp, WinGC, GetColor (DEF_COLOR));
323
324 for (e = loglist; e; e = e->next)
325 {
326 char **missing_charset_list;
327 int missing_charset_count;
328 char *def_string;
329
330 e->fontset = XCreateFontSet (disp, e->fontname,
331 &missing_charset_list, &missing_charset_count,
332 &def_string);
333 if (missing_charset_count)
334 {
335 #if 0
336 fprintf (stderr,
337 "Missing charsets in String to FontSet conversion (%s)\n",
338 missing_charset_list[0]);
339 #endif
340 XFreeStringList (missing_charset_list);
341 }
342
343 if (!e->fontset)
344 {
345 fprintf (stderr, "unable to create fontset for font '%s', exiting.\n", e->fontname);
346 exit (1);
347 }
348
349 {
350 XFontSetExtents *xfe = XExtentsOfFontSet (e->fontset);
351
352 e->font_height = xfe->max_logical_extent.height;
353 e->font_ascent = -xfe->max_logical_extent.y;
354 }
355
356 if (e->font_height > height - effect_y_space)
357 {
358 fprintf(stderr, "\n the display isn't tall enough to display a single line in font '%s'\n",
359 e->fontname);
360 fprintf(stderr, "\n the geometry in use is %d pixels tall\n", height);
361 fprintf(stderr, "\n font '%s' is %d pixels tall\n", e->fontname, e->font_height);
362 if (effect_y_space)
363 fprintf(stderr, "\n the shade or outline options need an extra %d pixel%s of vertical space\n",
364 effect_y_space, effect_y_space == 1 ? "" : "s");
365 fprintf(stderr, "\n");
366 exit(1);
367 }
368 }
369
370 if (geom_mask & XNegative)
371 win_x = win_x + ScreenWidth - width;
372 if (geom_mask & YNegative)
373 win_y = win_y + ScreenHeight - height;
374
375 {
376 struct logfile_entry *e;
377
378 for (e = loglist; e; e = e->next)
379 e->color = GetColor (e->colorname);
380 }
381
382 XSelectInput (disp, root, ExposureMask | FocusChangeMask);
383 }
384
385 /*
386 * if redraw () is passwd a non-zero argument, it does a complete
387 * redraw, rather than an update. if the argument is zero (and
388 * -noflicker is in effect) then only the lines which have changed
389 * since the last draw are redrawn.
390 *
391 * the rest is handled by regular refresh ()'es
392 */
393 static void
394 redraw (int redraw_all)
395 {
396 XSetClipMask (disp, WinGC, None);
397 refresh (0, 32768, 1, redraw_all);
398 }
399
400 static void
401 draw_text (Display *disp, Window root, GC WinGC, int x, int y, struct line_node *line, int foreground)
402 {
403 if (line->wrapped_right && opt_justify && line->breaks)
404 {
405 int i;
406 for (i = 0; i < line->num_words; i++)
407 XmbDrawString (disp, root, line->logfile->fontset, WinGC,
408 x + line->breaks[i].width + ((i * line->free_pixels) / (line->num_words - 1))
409 + continuation_width * line->wrapped_left, y,
410 line->line + line->breaks[i].index,
411 line->breaks[i].len);
412
413 if (line->wrapped_left)
414 {
415 if (foreground) XSetForeground (disp, WinGC, continuation_color);
416 XmbDrawString (disp, root, line->logfile->fontset, WinGC, x, y, continuation, continuation_length);
417 }
418 }
419 else
420 {
421 XmbDrawString (disp, root, line->logfile->fontset, WinGC, x + continuation_width * line->wrapped_left,
422 y, line->line, line->len);
423
424 if (line->wrapped_left)
425 {
426 if (foreground) XSetForeground (disp, WinGC, continuation_color);
427 XmbDrawString (disp, root, line->logfile->fontset, WinGC, x, y, continuation, continuation_length);
428 }
429 }
430 }
431
432 /* Just redraw everything without clearing (i.e. after an EXPOSE event) */
433 static void
434 refresh (int miny, int maxy, int clear, int refresh_all)
435 {
436 int lin = 0;
437 int space = height;
438 int offset;
439 unsigned long black_color = GetColor ("black");
440 struct line_node *line;
441 int step_per_line;
442 int foreground = 0;
443
444 if (opt_reverse)
445 offset = effect_y_offset;
446 else
447 offset = height + effect_y_offset;
448
449 miny -= win_y;
450 maxy -= win_y;
451
452 if (clear && !opt_noflicker)
453 XClearArea (disp, root, win_x, win_y, width + MARGIN_OF_ERROR, height, False);
454
455 for (line = linelist; line; line = line->next, lin++)
456 {
457 struct displaymatrix *display_line;
458
459 if (opt_noflicker && lin >= listlen)
460 {
461 int i = listlen;
462 listlen *= 1.5;
463 display = xrealloc(display, listlen * sizeof(struct displaymatrix));
464 for (; i < listlen; i++)
465 {
466 display[i].line = xstrdup ("");
467 display[i].len = 0;
468 display[i].offset = 0;
469 display[i].buffer_size = 0;
470 }
471 }
472
473 display_line = display + lin;
474
475 step_per_line = line->logfile->font_height + effect_y_space;
476 if (step_per_line > space)
477 break;
478
479 if (!opt_reverse)
480 offset -= step_per_line;
481
482 offset += line->logfile->font_ascent;
483
484 miny -= line->logfile->font_height;
485 maxy += line->logfile->font_height;
486
487 if (offset >= miny && offset <= maxy)
488 {
489 /* if this line is a different than it was, then it
490 * needs displaying */
491 if (!opt_noflicker
492 || refresh_all
493 || display_line->len != line->len
494 || display_line->color != line->logfile->color
495 || display_line->offset != offset
496 || memcmp (display_line->line, line->line, line->len))
497 {
498 /* don't bother updating the record of what has been
499 * displayed if -noflicker isn't in effect, since we redraw
500 * the whole display every time anyway */
501 if (opt_noflicker)
502 {
503 /* update the record of what has been displayed;
504 * first make sure the buffer is big enough */
505 if (display_line->buffer_size < line->len)
506 {
507 display_line->buffer_size = line->len;
508 display_line->line = xrealloc (display_line->line, display_line->buffer_size);
509 }
510
511 display_line->len = line->len;
512 display_line->color = line->logfile->color;
513 display_line->offset = offset;
514 memcpy (display_line->line, line->line, line->len);
515
516 if (clear)
517 {
518 #ifdef DEBUG
519 static int toggle;
520 toggle = 1 - toggle;
521 XSetForeground (disp, WinGC, toggle ? GetColor ("cyan") : GetColor ("yellow"));
522 XFillRectangle (disp, root, WinGC, win_x, win_y + offset - line->logfile->font_ascent,
523 width, step_per_line);
524 #else /* DEBUG */
525 XClearArea (disp, root, win_x, win_y + offset - line->logfile->font_ascent,
526 width + MARGIN_OF_ERROR, step_per_line, False);
527 #endif /* DEBUG */
528 }
529 }
530
531 if (opt_outline)
532 {
533 int x, y;
534 XSetForeground (disp, WinGC, black_color);
535
536 for (x = -1; x <= 1; x += 2)
537 for (y = -1; y <= 1; y += 2)
538 draw_text (disp, root, WinGC,
539 win_x + effect_x_offset + x,
540 win_y + y + offset, line, foreground = 0);
541 }
542 else if (opt_shade)
543 {
544 XSetForeground (disp, WinGC, black_color);
545 draw_text (disp, root, WinGC,
546 win_x + effect_x_offset + SHADE_X,
547 win_y + offset + SHADE_Y, line, foreground = 0);
548 }
549
550 XSetForeground (disp, WinGC, line->logfile->color);
551 draw_text (disp, root, WinGC,
552 win_x + effect_x_offset,
553 win_y + offset, line, foreground = 1);
554 }
555 }
556
557 if (opt_reverse)
558 offset += step_per_line;
559 offset -= line->logfile->font_ascent;
560
561 miny += line->logfile->font_height;
562 maxy -= line->logfile->font_height;
563
564 space -= step_per_line;
565 }
566
567 if (space > 0 && clear)
568 {
569 #ifdef DEBUG
570 XSetForeground (disp, WinGC, GetColor ("orange"));
571 XFillRectangle (disp, root, WinGC, win_x, win_y + offset - (opt_reverse ? 0 : space),
572 width, space);
573 #else /* DEBUG */
574 XClearArea (disp, root, win_x, win_y + offset - (opt_reverse ? 0 : space),
575 width + MARGIN_OF_ERROR, space, False);
576 #endif
577 }
578
579 /* at least one of the lines must fit in the allocated area. we've
580 * already checked at initialisation time that all the fonts are small
581 * enough to fit at least one line in the display area, but assert it
582 * again here to be sure */
583 assert(line != linelist);
584
585 /* any lines that didn't just get looked at are never going to be, so break the chain */
586 if (line) line->prev->next = 0;
587
588 /* and throw them all away */
589 while (line)
590 {
591 struct line_node *this = line;
592 line = line->next;
593 if (this->logfile && this->logfile->last == this)
594 this->logfile->last = NULL;
595 free (this->line);
596 free (this->breaks);
597 free (this);
598 }
599
600 if (opt_frame)
601 {
602 XSetForeground (disp, WinGC, GetColor (def_color));
603 /* note that XDrawRectangle() draws a rectangle one pixel bigger
604 * in both dimensions than you ask for, hence the subtractions.
605 * XFillRectangle() doesn't suffer from this problem */
606 XDrawRectangle (disp, root, WinGC, win_x - 0, win_y - 0, width - 1, height - 1);
607 }
608 }
609
610 #if HAS_REGEX
611 void void
612 transform_line (char *s)
613 {
614 #ifdef I_AM_Md
615 int i;
616 if (1)
617 {
618 for (i = 16; s[i]; i++)
619 s[i] = s[i + 11];
620 }
621 s[i + 1] = '\0';
622 #endif
623
624 if (transformre)
625 {
626 int i;
627 regmatch_t matched[16];
628
629 i = regexec (transformre, s, 16, matched, 0);
630 if (i == 0)
631 { /* matched */
632 int match_start = matched[0].rm_so;
633 int match_end = matched[0].rm_eo;
634 int old_len = match_end - match_start;
635 int new_len = strlen (transform_to);
636 int old_whole_len = strlen (s);
637
638 printf ("regexp was matched by '%s' - replace with '%s'\n", s, transform_to);
639 printf ("match is from %d to %d\n", match_start, match_end);
640 if (new_len > old_len)
641 s = xrealloc (s, old_whole_len + new_len - old_len);
642
643 if (new_len != old_len)
644 {
645 memcpy (s + match_end + new_len - old_len,
646 s + match_end,
647 old_whole_len - match_end);
648 s[old_whole_len + new_len - old_len] = '\0';
649 }
650
651 memcpy (s + match_start,
652 transform_to,
653 new_len);
654 printf ("transformed to '%s'\n", s);
655 }
656 else
657 printf ("regexp was not matched by '%s'\n", s);
658 }
659 }
660 #endif
661
662 /*
663 * appends p2 to the end of p1, if p1 is not null
664 * otherwise allocates a new string and copies p2 to it
665 */
666 static char *
667 concat_line (char *p1, const char *p2)
668 {
669 int l1 = p1 ? strlen (p1) : 0;
670 int l2 = strlen (p2);
671 char *r;
672
673 assert (p2);
674
675 if (p1)
676 r = xrealloc(p1, l1 + l2 + 1);
677 else
678 r = xmalloc (l2 + 1);
679
680 memcpy (r + l1, p2, l2);
681 r[l1 + l2] = 0;
682
683 return r;
684 }
685
686 /*
687 * This routine can read a line of any length if it is called enough times.
688 */
689 static int
690 lineinput (struct logfile_entry *logfile)
691 {
692 char buff[1024], *p;
693 int ch;
694 /* HACK-2: add on the length of any partial line which we will be appending to */
695 int ofs = logfile->buf ? strlen (logfile->buf) : 0;
696
697 /* this loop ensures that the whole line is read, even if it's
698 * longer than the buffer. we need to do this because when --whole
699 * is in effect we don't know whether to display the line or not
700 * until we've seen how (ie. whether) it ends */
701 do
702 {
703 p = buff;
704 do
705 {
706 ch = fgetc (logfile->fp);
707
708 if (ch == '\n' || ch == EOF)
709 break;
710 else if (ch == '\r')
711 continue; /* skip */
712 else if (ch == '\t')
713 {
714 do
715 {
716 *p++ = ' ';
717 ofs++;
718 }
719 while (ofs & 7);
720 }
721 else
722 {
723 *p++ = ch;
724 ofs++;
725 }
726 }
727 while (p < buff + (sizeof buff) - 8 - 1);
728
729 if (p == buff && ch == EOF)
730 return 0;
731
732 *p = 0;
733
734 p = logfile->buf = concat_line (logfile->buf, buff);
735 }
736 while (ch != '\n' && ch != EOF);
737
738 logfile->lastpartial = logfile->partial;
739 /* there are 3 ways we could have exited the loop: reading '\n',
740 * reaching EOF, or filling the buffer; the 2nd and 3rd of these
741 * both result in a partial line */
742 logfile->partial = ch != '\n';
743
744 if (logfile->partial && opt_whole)
745 return 0;
746
747 #if HAS_REGEX
748 transform_line (logfile->buf);
749 #endif
750 return 1;
751 }
752
753 /* input: reads file->fname
754 * output: fills file->fp, file->inode
755 * returns file->fp
756 * in case of error, file->fp is NULL
757 */
758 static FILE *
759 openlog (struct logfile_entry *file)
760 {
761 struct stat stats;
762
763 if ((file->fp = fopen (file->fname, "r")) == NULL)
764 {
765 file->fp = NULL;
766 return NULL;
767 }
768
769 fstat (fileno (file->fp), &stats);
770 if (S_ISFIFO (stats.st_mode))
771 {
772 if (fcntl (fileno (file->fp), F_SETFL, O_NONBLOCK) < 0)
773 perror ("fcntl"), exit (1);
774 file->inode = 0;
775 }
776 else
777 file->inode = stats.st_ino;
778
779 if (opt_noinitial)
780 fseek (file->fp, 0, SEEK_END);
781 else /* if (stats.st_size > (listlen + 1) * width)
782 * HACK - 'width' is in pixels - how are we to know how much text will fit?
783 * fseek (file->fp, -((listlen + 2) * width/10), SEEK_END); */
784 fseek (file->fp, -5000, SEEK_END);
785
786 file->last_size = stats.st_size;
787 return file->fp;
788 }
789
790 static void
791 reopen (void)
792 {
793 struct logfile_entry *e;
794
795 for (e = loglist; e; e = e->next)
796 {
797 if (!e->inode)
798 continue; /* skip stdin */
799
800 if (e->fp)
801 fclose (e->fp);
802 /* if fp is NULL we will try again later */
803 openlog (e);
804 }
805
806 do_reopen = 0;
807 }
808
809 static void
810 check_open_files (void)
811 {
812 struct logfile_entry *e;
813 struct stat stats;
814
815 for (e = loglist; e; e = e->next)
816 {
817 if (!e->inode)
818 continue; /* skip stdin */
819
820 if (stat (e->fname, &stats) < 0)
821 { /* file missing? */
822 sleep (1);
823 if (e->fp)
824 fclose (e->fp);
825 if (openlog (e) == NULL)
826 continue;
827 if (fstat (fileno (e->fp), &stats) < 0)
828 continue;
829 }
830
831 if (stats.st_ino != e->inode)
832 { /* file renamed? */
833 if (e->fp)
834 fclose (e->fp);
835 if (openlog (e) == NULL)
836 continue;
837 if (fstat (fileno (e->fp), &stats) < 0)
838 continue;
839 }
840
841 if (stats.st_size < e->last_size)
842 { /* file truncated? */
843 fseek (e->fp, 0, SEEK_SET);
844 e->last_size = stats.st_size;
845 }
846 }
847 }
848
849 /*
850 * insert a single node in the list of screen lines and return a
851 * pointer to the new node.
852 * the caller MUST then fill in ret->line and ret->len with valid
853 * data.
854 */
855 static struct line_node *
856 new_line_node (struct logfile_entry *log)
857 {
858 struct line_node *new = xmalloc (sizeof (struct line_node));
859
860 new->logfile = log;
861 new->wrapped_left = 0;
862 new->wrapped_right = 0;
863 new->breaks = 0;
864
865 assert(log);
866
867 if (!log || !log->last)
868 {
869 new->next = linelist;
870 new->next->prev = new;
871
872 new->prev = NULL;
873 linelist = new;
874 }
875 else
876 {
877 /* 2 pointers from the new node */
878 new->next = log->last;
879 new->prev = log->last->prev;
880
881 /* 2 pointers back to the new node */
882 if (new->next) new->next->prev = new;
883 if (new->prev) new->prev->next = new;
884
885 /* if this is a new first entry in the list then update
886 * 'linelist' */
887 if (log->last == linelist)
888 linelist = new;
889 }
890
891 /* update the logfile record */
892 if (log)
893 log->last = new;
894
895 return new;
896 }
897
898 /*
899 * this is called after either adding a new line or appending to an
900 * old one. in both cases it's possible that the line no longer fits,
901 * and needs wrapping. this function checks the last line associated
902 * with the supplied logfile.
903 */
904 static void
905 possibly_split_long_line (struct logfile_entry *log)
906 {
907 char *str = log->last->line;
908 int l = strlen (str);
909 char *p = str;
910 struct line_node *line;
911 int spaces;
912 static struct breakinfo *breaks;
913 static int break_buffer_size;
914
915 /* only calculate the continuation's width once */
916 if (continuation_width == -1)
917 {
918 continuation_length = strlen (continuation);
919 continuation_width = XmbTextEscapement (log->fontset, continuation, continuation_length);
920 continuation_color = GetColor (cont_color);
921
922 /* make an array to store information about the location of
923 * spaces in the line */
924 if (opt_justify)
925 {
926 break_buffer_size = 32;
927 breaks = xmalloc (break_buffer_size * sizeof (struct breakinfo));
928 }
929 }
930
931 do
932 {
933 const char *beg = p;
934 int start_w = log->last->wrapped_left ? continuation_width : 0;
935 int w = start_w;
936 int wrapped = 0;
937 char *break_p = NULL;
938 int width_at_break_p = 0;
939 int prefix_len;
940
941 spaces = 0;
942
943 if (opt_justify)
944 breaks[spaces].index = breaks[spaces].width = 0;
945
946 while (*p)
947 {
948 int cw, len;
949
950 /* find the length in bytes of the next multibyte character */
951 len = mblen (p, l);
952 if (len <= 0)
953 len = 1; /* ignore (don't skip) illegal character sequences */
954
955 /* find the width in pixels of the next character */
956 cw = XmbTextEscapement (log->fontset, p, len);
957 if (opt_wordwrap && len == 1 && p[0] == ' ' && p != break_p + 1)
958 {
959 break_p = p;
960 width_at_break_p = w;
961 spaces++;
962
963 if (opt_justify)
964 {
965 /* increase the size of the 'breaks' array when
966 * necessary */
967 if (spaces >= break_buffer_size)
968 {
969 break_buffer_size *= 1.5;
970 breaks = xrealloc (breaks, break_buffer_size * sizeof (struct breakinfo));
971 }
972
973 /* store information about (a) the location of each
974 * space */
975 breaks[spaces].index = p + 1 - beg;
976 /* (b) the width (in pixels) of the string up to
977 * this space */
978 breaks[spaces].width = cw + w - start_w;
979 /* (c) the length of each 'word' */
980 breaks[spaces-1].len = breaks[spaces].index - breaks[spaces-1].index;
981 }
982 }
983
984 if (cw + w > width - effect_x_space)
985 {
986 if (p == beg)
987 {
988 fprintf (stderr, "we can't even fit a single character onto the line\n");
989 if (len == 1) fprintf (stderr, "(the character we couldn't fit was '%c')\n", *p);
990 exit (1);
991 }
992
993 wrapped = 1;
994 break;
995 }
996
997 w += cw;
998 p += len;
999 l -= len;
1000 }
1001
1002 /* if we're wrapping at spaces, and the line is long enough to
1003 * wrap, and we've seen a space already, and the space wasn't
1004 * the first character on the line, then wrap at the space */
1005 if (!wrapped)
1006 break;
1007
1008 /* choose where to break the line */
1009 if (opt_wordwrap && break_p && break_p != beg)
1010 {
1011 prefix_len = break_p - beg;
1012 p = break_p;
1013 w = width_at_break_p;
1014
1015 /* if breaking at a space, skip all adjacent spaces */
1016 while (*p == ' ')
1017 {
1018 int len = mblen (p, l);
1019 if (len != 1) break;
1020 p++;
1021 }
1022
1023 if (opt_justify)
1024 {
1025 spaces--;
1026 breaks[spaces].len--;
1027 }
1028 }
1029 else
1030 prefix_len = p - beg;
1031
1032 /* make a copy of the tail end of the string */
1033 p = xstrdup (p);
1034
1035 /* and reduce the size of the head of the string */
1036 log->last->line = xrealloc (log->last->line, prefix_len + 1);
1037 log->last->len = prefix_len;
1038 log->last->line[prefix_len] = '\0';
1039
1040 /* note that the head was wrapped on it's right */
1041 log->last->wrapped_right = 1;
1042
1043 /* 'spaces' includes any space we broke on; we can only justify
1044 * if there's at least one other space */
1045 if (opt_justify && spaces &&
1046 width - effect_x_space - width_at_break_p < spaces * log->font_height)
1047 {
1048 int i;
1049 log->last->free_pixels = width - effect_x_space - w;
1050 log->last->num_words = spaces + 1;
1051 log->last->breaks = malloc (log->last->num_words * sizeof (struct breakinfo));
1052 for (i = 0; i < log->last->num_words; i++)
1053 log->last->breaks[i] = breaks[i];
1054 }
1055
1056 line = new_line_node (log);
1057 line->line = p;
1058 l = line->len = strlen (p);
1059
1060 /* note that the tail end of the string is wrapped at its left */
1061 line->wrapped_left = 1;
1062 }
1063 while (l);
1064 }
1065
1066 static void
1067 insert_new_line (char *str, struct logfile_entry *log)
1068 {
1069 struct line_node *new;
1070 new = new_line_node (log);
1071 new->line = str;
1072 new->len = strlen (str);
1073
1074 possibly_split_long_line (log);
1075 }
1076
1077 /*
1078 * append something to an existing physical line. this is done
1079 * by deleting the file on-screen, concatenating the new data to it
1080 * and splitting it again.
1081 */
1082 static void
1083 append_to_existing_line (char *str, struct logfile_entry *log)
1084 {
1085 char *old, *new;
1086
1087 assert(log);
1088 assert(log->last);
1089
1090 old = log->last->line;
1091 assert(old);
1092
1093 new = concat_line (old, str);
1094 free (str);
1095 log->last->line = new;
1096 log->last->len = strlen (new);
1097 possibly_split_long_line (log);
1098 }
1099
1100 static void
1101 main_loop (void)
1102 {
1103 int lin;
1104 time_t lastreload;
1105 Region region = XCreateRegion ();
1106 XEvent xev;
1107 struct logfile_entry *lastprinted = NULL;
1108 struct logfile_entry *current;
1109 int need_update = 1;
1110
1111 display = xmalloc (sizeof (struct displaymatrix) * listlen);
1112
1113 lastreload = time (NULL);
1114
1115 /* Initialize line_node */
1116 for (lin = 0; lin < listlen; lin++)
1117 {
1118 struct line_node *e = xmalloc (sizeof (struct line_node));
1119 e->line = xstrdup ("~");
1120 e->len = 1;
1121 e->logfile = loglist; /* this is only needed to get a color for the '~' */
1122 e->wrapped_left = 0;
1123 e->wrapped_right = 0;
1124 e->breaks = 0;
1125 e->next = NULL;
1126 e->prev = linelist_tail;
1127
1128 if (!linelist)
1129 linelist = e;
1130 if (linelist_tail)
1131 linelist_tail->next = e;
1132 linelist_tail = e;
1133
1134 display[lin].line = xstrdup ("");
1135 display[lin].len = 0;
1136 display[lin].offset = 0;
1137 display[lin].buffer_size = 0;
1138 }
1139
1140 for (;;)
1141 {
1142 /* read logs */
1143 for (current = loglist; current; current = current->next)
1144 {
1145 if (!current->fp)
1146 continue; /* skip missing files */
1147
1148 clearerr (current->fp);
1149
1150 while (lineinput (current))
1151 {
1152 need_update = 1;
1153 /* if we're trying to update old partial lines in
1154 * place, and the last time this file was updated the
1155 * output was partial, and that partial line is not
1156 * too close to the top of the screen, then update
1157 * that partial line */
1158 if (opt_update && current->lastpartial && current->last)
1159 {
1160 append_to_existing_line (current->buf, current);
1161 current->buf = 0;
1162 continue;
1163 }
1164
1165 /* if all we just read was a newline ending a line that we've already displayed, skip it */
1166 if (current->buf[0] == '\0' && current->lastpartial)
1167 {
1168 free(current->buf);
1169 current->buf = 0;
1170 continue;
1171 }
1172
1173 /* print filename if any, and if last line was from
1174 * different file */
1175 if (lastprinted != current)
1176 {
1177 current->last = 0;
1178 if (!opt_nofilename && current->desc[0])
1179 {
1180 insert_new_line (xstrdup ("["), current);
1181 append_to_existing_line (xstrdup (current->desc), current);
1182 append_to_existing_line (xstrdup ("]"), current);
1183 }
1184 }
1185
1186 /* if we're dealing with partial lines, and the last
1187 * time we showed the line it wasn't finished ... */
1188 if (!opt_whole && current->lastpartial)
1189 {
1190 /* if this is the same file we showed last then
1191 append to the last line shown */
1192 if (lastprinted == current)
1193 append_to_existing_line (current->buf, current);
1194 else
1195 {
1196 /* but if a different file has been shown in the
1197 * mean time, make a new line, starting with the
1198 * continuation string */
1199 insert_new_line (current->buf, current);
1200 current->last->wrapped_left = 1;
1201 }
1202 }
1203 else
1204 /* otherwise just make a plain and simple new line */
1205 insert_new_line (current->buf, current);
1206
1207 current->buf = 0;
1208 lastprinted = current;
1209 }
1210 }
1211
1212 if (need_update)
1213 {
1214 redraw (0);
1215 need_update = 0;
1216 }
1217 else
1218 {
1219 XFlush (disp);
1220
1221 if (!XPending (disp))
1222 {
1223 fd_set fdr;
1224 struct timeval to = interval;
1225
1226 FD_ZERO (&fdr);
1227 FD_SET (ConnectionNumber (disp), &fdr);
1228 select (ConnectionNumber (disp) + 1, &fdr, 0, 0, &to);
1229 }
1230 }
1231
1232 check_open_files ();
1233
1234 if (do_reopen)
1235 reopen ();
1236
1237 /* we ignore possible errors due to window resizing &c */
1238 while (XPending (disp))
1239 {
1240 XNextEvent (disp, &xev);
1241
1242 switch (xev.type)
1243 {
1244 case Expose:
1245 {
1246 XRectangle r;
1247
1248 r.x = xev.xexpose.x;
1249 r.y = xev.xexpose.y;
1250 r.width = xev.xexpose.width;
1251 r.height = xev.xexpose.height;
1252
1253 XUnionRectWithRegion (&r, region, region);
1254 }
1255 break;
1256 default:
1257 #ifdef DEBUGMODE
1258 fprintf (stderr, "PANIC! Unknown event %d\n", xev.type);
1259 #endif
1260 break;
1261 }
1262 }
1263
1264 /* reload if requested */
1265 if (reload && lastreload + reload < time (NULL))
1266 {
1267 if (command && command[0])
1268 system (command);
1269
1270 reopen ();
1271 lastreload = time (NULL);
1272 }
1273
1274 if (!XEmptyRegion (region))
1275 {
1276 XRectangle r;
1277
1278 XSetRegion (disp, WinGC, region);
1279 XClipBox (region, &r);
1280
1281 refresh (r.y, r.y + r.height, 0, 1);
1282
1283 XDestroyRegion (region);
1284 region = XCreateRegion ();
1285 }
1286 }
1287 }
1288
1289 int
1290 main (int argc, char *argv[])
1291 {
1292 int i;
1293 int opt_daemonize = 0;
1294 int opt_partial = 0, file_count = 0;
1295 #if HAS_REGEX
1296 char *transform = NULL;
1297 #endif
1298
1299 setlocale (LC_CTYPE, ""); /* try to initialize the locale. */
1300
1301 for (i = 1; i < argc; i++)
1302 {
1303 const char *arg = argv[i];
1304
1305 if (arg[0] == '-' && arg[1] != '\0' && arg[1] != ',')
1306 {
1307 if (arg[1] == '-')
1308 arg++;
1309
1310 if (!strcmp (arg, "-?") ||
1311 !strcmp (arg, "-help") || !strcmp (arg, "-h"))
1312 display_help (argv[0]);
1313 else if (!strcmp (arg, "-V"))
1314 display_version ();
1315 else if (!strcmp (arg, "-g") || !strcmp (arg, "-geometry"))
1316 geom_mask =
1317 XParseGeometry (argv[++i], &win_x, &win_y, &width, &height);
1318 else if (!strcmp (arg, "-display"))
1319 dispname = argv[++i];
1320 else if (!strcmp (arg, "-cont"))
1321 continuation = argv[++i];
1322 else if (!strcmp (arg, "-cont-color"))
1323 cont_color = argv[++i];
1324 else if (!strcmp (arg, "-font") || !strcmp (arg, "-fn"))
1325 fontname = argv[++i];
1326 #if HAS_REGEX
1327 else if (!strcmp (arg, "-t"))
1328 {
1329 transform = argv[++i];
1330 transform_to = argv[++i];
1331 printf ("transform: '%s' to '%s'\n", transform, transform_to);
1332 }
1333 #endif
1334 else if (!strcmp (arg, "-fork") || !strcmp (arg, "-f"))
1335 opt_daemonize = 1;
1336 else if (!strcmp (arg, "-reload"))
1337 {
1338 reload = atoi (argv[++i]);
1339 command = argv[++i];
1340 }
1341 else if (!strcmp (arg, "-windowed"))
1342 opt_windowed = 1;
1343 else if (!strcmp (arg, "-shade"))
1344 opt_shade = 1;
1345 else if (!strcmp (arg, "-outline"))
1346 opt_outline = 1;
1347 else if (!strcmp (arg, "-minspace"))
1348 opt_minspace = 1;
1349 else if (!strcmp (arg, "-noflicker"))
1350 opt_noflicker = 1;
1351 else if (!strcmp (arg, "-frame"))
1352 opt_frame = 1;
1353 else if (!strcmp (arg, "-no-filename"))
1354 opt_nofilename = 1;
1355 else if (!strcmp (arg, "-reverse"))
1356 opt_reverse = 1;
1357 else if (!strcmp (arg, "-whole"))
1358 opt_whole = 1;
1359 else if (!strcmp (arg, "-partial"))
1360 opt_partial = 1;
1361 else if (!strcmp (arg, "-update"))
1362 opt_update = opt_partial = 1;
1363 else if (!strcmp (arg, "-wordwrap"))
1364 opt_wordwrap = 1;
1365 else if (!strcmp (arg, "-justify"))
1366 opt_justify = 1;
1367 else if (!strcmp (arg, "-color"))
1368 def_color = argv[++i];
1369 else if (!strcmp (arg, "-noinitial"))
1370 opt_noinitial = 1;
1371 else if (!strcmp (arg, "-id"))
1372 {
1373 unsigned long id;
1374
1375 if (sscanf (argv[++i], "%li", &id) == 1 && id)
1376 root = id;
1377 }
1378 else if (!strcmp (arg, "-interval") || !strcmp (arg, "-i"))
1379 {
1380 double iv = atof (argv[++i]);
1381
1382 interval.tv_sec = (int) iv;
1383 interval.tv_usec = (iv - interval.tv_sec) * 1e6;
1384 }
1385 else
1386 {
1387 fprintf (stderr, "Unknown option '%s'.\n"
1388 "Try --help for more information.\n", arg);
1389 exit (1);
1390 }
1391 }
1392 else
1393 { /* it must be a filename */
1394 struct logfile_entry *e;
1395 const char *fname, *desc, *fcolor = def_color;
1396 char *p;
1397
1398 file_count++;
1399
1400 /* this is not foolproof yet (',' in filenames are not allowed) */
1401 fname = desc = arg;
1402 if ((p = strchr (arg, ',')))
1403 {
1404 *p = '\0';
1405 fcolor = p + 1;
1406
1407 if ((p = strchr (fcolor, ',')))
1408 {
1409 *p = '\0';
1410 desc = p + 1;
1411 }
1412 }
1413
1414 e = xmalloc (sizeof (struct logfile_entry));
1415 e->partial = 0;
1416 e->buf = 0;
1417
1418 if (arg[0] == '-' && arg[1] == '\0')
1419 {
1420 if ((e->fp = fdopen (0, "r")) == NULL)
1421 perror ("fdopen"), exit (1);
1422 if (fcntl (0, F_SETFL, O_NONBLOCK) < 0)
1423 perror ("fcntl"), exit (1);
1424
1425 e->fname = NULL;
1426 e->inode = 0;
1427 if (desc == arg)
1428 e->desc = xstrdup ("stdin");
1429 else
1430 e->desc = xstrdup (desc);
1431 }
1432 else
1433 {
1434 e->fname = xstrdup (fname);
1435
1436 if (openlog (e) == NULL)
1437 perror (fname), exit (1);
1438
1439 e->desc = xstrdup (desc);
1440 }
1441
1442 e->colorname = fcolor;
1443 e->partial = 0;
1444 e->fontname = fontname;
1445 e->last = NULL;
1446 e->next = NULL;
1447
1448 if (!loglist)
1449 loglist = e;
1450 if (loglist_tail)
1451 loglist_tail->next = e;
1452
1453 loglist_tail = e;
1454 }
1455 }
1456
1457 if (!loglist)
1458 {
1459 fprintf (stderr, "You did not specify any files to tail\n"
1460 "use %s --help for help\n", argv[0]);
1461 exit (1);
1462 }
1463
1464 if (opt_update && opt_whole)
1465 {
1466 fprintf (stderr, "Specify at most one of -update and -whole\n");
1467 exit (1);
1468 }
1469 else if (opt_partial && opt_whole)
1470 {
1471 fprintf (stderr, "Specify at most one of -partial and -whole\n");
1472 exit (1);
1473 }
1474
1475 /* it doesn't make sense to justify if word wrap isn't on */
1476 if (opt_justify)
1477 opt_wordwrap = 1;
1478
1479 /* HACK-7: do we want to allow both -shade and -outline? */
1480 if (opt_shade && opt_outline)
1481 {
1482 fprintf (stderr, "Specify at most one of -shade and -outline\n");
1483 exit (1);
1484 }
1485
1486 if (opt_partial)
1487 /* if we specifically requested to see partial lines then don't insist on whole lines */
1488 opt_whole = 0;
1489 else if (file_count > 1)
1490 /* otherwise, if we're viewing multiple files, default to showing whole lines */
1491 opt_whole = 1;
1492
1493 #if HAS_REGEX
1494 if (transform)
1495 {
1496 int i;
1497
1498 printf ("compiling regexp '%s'\n", transform);
1499 transformre = xmalloc (sizeof (regex_t));
1500 i = regcomp (transformre, transform, REG_EXTENDED);
1501 if (i != 0)
1502 {
1503 char buf[512];
1504
1505 regerror (i, transformre, buf, sizeof (buf));
1506 fprintf (stderr, "Cannot compile regular expression: %s\n", buf);
1507 }
1508 else
1509 printf ("compiled '%s' OK to %x\n", transform, (int)transformre);
1510 }
1511 #endif
1512
1513 if (opt_outline && !opt_minspace)
1514 {
1515 /* adding outline increases the total width and height by 2
1516 pixels each, and offsets the text one pixel right and one
1517 pixel down */
1518 effect_x_space = effect_y_space = 2;
1519 effect_x_offset = effect_y_offset = 1;
1520 }
1521 else if (opt_shade && !opt_minspace)
1522 {
1523 /* adding a shadow increases the space used */
1524 effect_x_space = abs (SHADE_X);
1525 effect_y_space = abs (SHADE_Y);
1526
1527 /* if the shadow is to the right and below then we don't need
1528 * to move the text to make space for it, but shadows to the left
1529 * and above need accomodating */
1530 effect_x_offset = SHADE_X > 0 ? 0 : -SHADE_X;
1531 effect_y_offset = SHADE_Y > 0 ? 0 : -SHADE_Y;
1532 }
1533 else
1534 {
1535 effect_x_space = effect_y_space = 0;
1536 effect_x_offset = effect_y_offset = 0;
1537 }
1538
1539 InitWindow ();
1540
1541 install_signal (SIGINT, blank_window);
1542 install_signal (SIGQUIT, blank_window);
1543 install_signal (SIGTERM, blank_window);
1544 install_signal (SIGHUP, force_reopen);
1545 install_signal (SIGUSR1, list_files);
1546 install_signal (SIGUSR2, force_refresh);
1547
1548 if (opt_daemonize)
1549 daemonize ();
1550
1551 main_loop ();
1552
1553 exit (1); /* to make gcc -Wall stop complaining */
1554 }
1555
1556 static void
1557 install_signal (int sig, void (*handler) (int))
1558 {
1559 struct sigaction action;
1560
1561 action.sa_handler = handler;
1562 sigemptyset (&action.sa_mask);
1563 action.sa_flags = SA_RESTART;
1564
1565 if (sigaction (sig, &action, NULL) < 0)
1566 fprintf (stderr, "sigaction (%d): %s\n", sig, strerror (errno)), exit (1);
1567 }
1568
1569 static void *
1570 xstrdup (const char *string)
1571 {
1572 void *p;
1573
1574 while ((p = strdup (string)) == NULL)
1575 {
1576 fprintf (stderr, "Memory exhausted in xstrdup ().\n");
1577 sleep (10);
1578 }
1579
1580 return p;
1581 }
1582
1583 static void *
1584 xmalloc (size_t size)
1585 {
1586 void *p;
1587
1588 while ((p = malloc (size)) == NULL)
1589 {
1590 fprintf (stderr, "Memory exhausted in xmalloc ().\n");
1591 sleep (10);
1592 }
1593
1594 return p;
1595 }
1596
1597 static void *
1598 xrealloc (void *ptr, size_t size)
1599 {
1600 void *p;
1601
1602 while ((p = realloc (ptr, size)) == NULL)
1603 {
1604 fprintf (stderr, "Memory exhausted in xrealloc ().\n");
1605 sleep (10);
1606 }
1607
1608 return p;
1609 }
1610
1611 static void
1612 display_help (char *myname)
1613 {
1614 printf ("Usage: %s [options] file1[,color[,desc]]"
1615 "[options] [file2[,color[,desc]] ...]\n", myname);
1616 printf (" -g | -geometry geometry -g WIDTHxHEIGHT+X+Y\n"
1617 " -color color use color $color as default\n"
1618 " -reload sec command reload after $sec and run command\n"
1619 " -id id window id to use instead of the root window\n"
1620 " -windowed create a window instead of writing to the root\n"
1621 " -font FONTSPEC (-fn) font to use\n"
1622 " -f | -fork fork into background\n"
1623 " -reverse print new lines at the top\n"
1624 " -whole wait for \\n before showing a line\n"
1625 " -partial show lines even if they don't end with a \\n\n"
1626 " -update allow updates to old partial lines\n"
1627 " -cont string to prefix continued partial lines with\n"
1628 " defaults to \"|| \"\n"
1629 " -wordwrap wrap long lines at spaces to avoid breaking words\n"
1630 " -shade add shading to font\n"
1631 " -outline add black outline to font\n"
1632 " -minspace force minimum line spacing\n"
1633 " -noinitial don't display the last file lines on\n"
1634 " startup\n"
1635 " -i | -interval seconds interval between checks (fractional\n"
1636 " values o.k.). Default 2.4 seconds\n"
1637 " -V display version information and exit\n"
1638 "\n");
1639 printf ("Example:\n%s -g 800x250+100+50 -font fixed /var/log/messages,green "
1640 "/var/log/secure,red,'ALERT'\n", myname);
1641 exit (0);
1642 }
1643
1644 static void
1645 display_version (void)
1646 {
1647 printf ("root-tail version " VERSION "\n");
1648 exit (0);
1649 }
1650
1651 static int
1652 daemonize (void)
1653 {
1654 pid_t pid;
1655
1656 switch (pid = fork ())
1657 {
1658 case -1:
1659 return -1;
1660 case 0:
1661 break;
1662 default:
1663 /*printf ("%d\n", pid);*/
1664 exit (0);
1665 }
1666
1667 if (setsid () == -1)
1668 return -1;
1669
1670 return 0;
1671 }
1672