/*
* wtail - watch multiple files
* AYM 2002-03-23
*/
/*
This file is copyright André Majorel 2002-2003.
This program is free software; you can redistribute it and/or modify it under
the terms of version 2 of the GNU General Public License as published by the
Free Software Foundation.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place, Suite 330, Boston, MA 02111-1307, USA.
*/
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <curses.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#define IBUFSZ 16384 /* Read up to that many chars at a time */
#define OBUFSZ 512 /* Maximum number of characters that
it's safe to print with curses. The
exact value is unknown. A quick
search in SUS v2, the ncurses man
pages and n869 turned up nothing.
What I do know is that a value of
4096 can make ncurses drop
characters. */
typedef struct
{
const char *pathname;
int errno_;
int fd;
unsigned long line;
int datay;
int datah;
WINDOW *dataw;
int statusy;
WINDOW *statusw;
} file_t;
/* Assuming ISO 8859-* display */
#define MYISCTRL(c) ((((c) & 0x60) == 0 && (c) != 0x09 && (c) != 0x0a)\
|| (c) == 0x7f)
extern const char version[];
static void err (const char *fmt, ...);
static int wputsm (WINDOW *win, int height, const char *str);
int main (int argc, char *argv[])
{
int exit_status = 0;
size_t nfiles;
file_t *files = NULL;
float window_lines;
unsigned char *buf = NULL;
/* Parse the command line */
{
int g;
if (argc == 2 && strcmp (argv[1], "--help") == 0)
{
puts ("Usage:\n"
" wtail --help\n"
" wtail --version\n"
" wtail file ...");
puts ("Options:\n"
" --help Print usage to standard output and exit successfully\n"
" --version Print version number to standard output and exit successfully");
exit (0);
}
if (argc == 2 && strcmp (argv[1], "--version") == 0)
{
puts (version);
exit (0);
}
while ((g = getopt (argc, argv, "")) != EOF)
{
exit (1);
}
}
if (optind >= argc)
{
err ("not enough arguments");
exit (1);
}
nfiles = argc - optind;
files = malloc (nfiles * sizeof *files);
if (files == NULL)
{
err ("%s", strerror (ENOMEM));
exit (1);
}
{
size_t f;
for (f = 0; f < nfiles; f++)
{
files[f].pathname = argv[optind + f];
files[f].fd = -1;
files[f].errno_ = 0;
files[f].line = 0;
}
}
buf = malloc (IBUFSZ + 1);
if (buf == NULL)
{
err ("%s", strerror (ENOMEM));
exit (1);
}
initscr ();
cbreak ();
intrflush (stdscr, FALSE);
leaveok (stdscr, TRUE);
noecho ();
nonl ();
refresh ();
/* Create a window for each file */
if (LINES < 2 * nfiles)
{
err ("not enough lines or too many files"); /* FIXME goes to /dev/null */
exit_status = 1;
goto byebye;
}
window_lines = (float) LINES / nfiles;
{
size_t n;
int lines_so_far = 0;
for (n = 0; n < nfiles; n++)
{
int nlines;
if (n + 1 == nfiles)
nlines = LINES - lines_so_far;
else
{
if (lines_so_far < n * window_lines)
nlines = (int) window_lines + 1;
else
nlines = (int) window_lines;
}
if (nlines < 2)
{
err ("internal error");
exit (1);
}
files[n].datay = lines_so_far;
files[n].statusy = lines_so_far + nlines - 1;
files[n].datah = nlines - 1;
files[n].dataw = subwin (stdscr, files[n].datah, COLS,
files[n].datay, 0);
files[n].statusw = subwin (stdscr, 1, COLS, files[n].statusy, 0);
idlok (files[n].dataw, TRUE);
scrollok (files[n].dataw, TRUE);
wattron (files[n].statusw, A_REVERSE);
/* Initialise status bar */
mvwprintw (files[n].statusw, 0, 0, "%-*.*s",
(size_t) COLS, (size_t) COLS, files[n].pathname);
wnoutrefresh (files[n].statusw);
lines_so_far += nlines;
}
}
doupdate ();
nodelay (stdscr, TRUE);
/* Main loop : waiting for data on any of the files. Wake up
every 100 ms second to check the keyboard. */
for (;;)
{
size_t n;
int k;
fd_set readfds;
int fdmax = -1;
struct timeval timeout;
static int ateof = 0;
/* Poll the keyboard */
k = getch ();
if (k == 'q' || k == 'Q' || k == '\x11' || k == '\x1b')
break;
/* If the last time we tried to read, we got EOF on all
files, wait for a while before polling them again. */
if (ateof)
{
struct timeval nap;
nap.tv_sec = 0;
nap.tv_usec = 100000;
select (0, NULL, NULL, NULL, &nap);
}
/* Keep trying to open files */
{
size_t f;
for (f = 0; f < nfiles; f++)
{
if (files[f].fd < 0)
{
files[f].fd = open (files[f].pathname, O_RDONLY);
if (files[f].fd < 0)
{
/* Print the cause of the failure in the middle of the
data window. */
if (files[f].errno_ != errno)
{
wputsm (files[f].dataw, files[f].datah, strerror (errno));
wnoutrefresh (files[f].dataw);
files[f].errno_ = errno;
}
continue;
}
files[f].line = 0;
wclear (files[f].dataw);
wnoutrefresh (files[f].dataw);
}
}
}
/* Poll the files */
ateof = 1;
FD_ZERO (&readfds);
{
size_t f;
for (f = 0; f < nfiles; f++)
{
if (files[f].fd < 0)
continue;
FD_SET (files[f].fd, &readfds);
if (files[f].fd > fdmax)
fdmax = files[f].fd;
}
}
timeout.tv_sec = 0;
timeout.tv_usec = 0;
if (select (fdmax + 1, &readfds, NULL, NULL, &timeout) < 1)
continue;
for (n = 0; n < nfiles; n++)
{
if (files[n].fd < 0)
continue;
if (FD_ISSET (files[n].fd, &readfds))
{
unsigned long line = files[n].line;
ssize_t nbytes;
if ((nbytes = read (files[n].fd, buf, IBUFSZ)) > 0)
{
unsigned char *p = buf;
unsigned char *const pmax = buf + nbytes;
ateof = 0;
if (files[n].line == 0)
files[n].line = 1;
for (;;)
{
/* Print a run of printable characters */
{
unsigned char *p0 = p;
while (p < pmax && ! MYISCTRL (*p))
{
if (*p == '\n')
files[n].line++;
p++;
if (p >= p0 + OBUFSZ)
{
wprintw (files[n].dataw, "%.*s", (size_t) (p - p0), p0);
p0 = p;
}
}
if (p > p0)
wprintw (files[n].dataw, "%.*s", (size_t) (p - p0), p0);
#if 0
wattron (files[n].dataw, A_STANDOUT);
waddch (files[n].dataw, '#');
wattroff (files[n].dataw, A_STANDOUT);
#endif
if (p >= pmax)
break;
}
/* Print a run of control characters */
wattron (files[n].dataw, A_REVERSE);
while (p < pmax && MYISCTRL (*p))
{
if (*p >= 0x80)
{
waddch (files[n].dataw, '~');
waddch (files[n].dataw, *p - 0x40);
}
else if (*p == 0x7f)
{
waddstr (files[n].dataw, "^?");
}
else
{
waddch (files[n].dataw, '^');
waddch (files[n].dataw, *p + 0x40);
}
p++;
}
wattroff (files[n].dataw, A_REVERSE);
if (p >= pmax)
break;
}
wnoutrefresh (files[n].dataw);
}
/* If the line number has changed, refresh */
if (files[n].line != line)
{
int x;
line = files[n].line;
for (x = COLS - 1; x >= 0; x--)
{
mvwaddch (files[n].statusw, 0, x, '0' + line % 10);
line /= 10;
if (line == 0)
break;
}
wnoutrefresh (files[n].statusw);
}
}
}
doupdate ();
}
byebye:
/* FIXME close files[*].fd */
/* FIXME free files */
endwin ();
if (buf != NULL)
free (buf);
exit (exit_status);
}
static void err (const char *fmt, ...)
{
va_list argp;
fputs ("wtail: ", stderr);
va_start (argp, fmt);
vfprintf (stderr, fmt, argp);
va_end (argp);
fputc ('\n', stderr);
}
/*
* wputsm - write a string in the middle of a window
*/
static int wputsm (WINDOW *win, int height, const char *str)
{
size_t len = strlen (str);
int xpos = (COLS - len) / 2;
int strofs = 0;
if (xpos < 0)
{
strofs += xpos;
xpos = 0;
}
return mvwprintw (win, height / 2, xpos, "%.*s", (size_t) COLS, str + strofs);
}
syntax highlighted by Code2HTML, v. 0.9.1