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