procps/watch.c
Craig Small fb11e1fe0a Changed the err and warns to macros
err and warn are BSD format but they are not recommended by library
developers.  However their consiseness is useful!

The solution is to use some macros that create xerr etc which then
just map to the error() function.  The next problem is error() uses
program_invocation_name so we set this to program_invovation_short_name

This is a global set but seems to be the convention (or at least errors
are on the short name only) used everywhere else.
2012-01-03 18:48:43 +11:00

709 lines
16 KiB
C

/*
* watch -- execute a program repeatedly, displaying output fullscreen
*
* Based on the original 1991 'watch' by Tony Rems <rembo@unisoft.com>
* (with mods and corrections by Francois Pinard).
*
* Substantially reworked, new features (differences option, SIGWINCH
* handling, unlimited command length, long line handling) added Apr
* 1999 by Mike Coleman <mkc@acm.org>.
*
* Changes by Albert Cahalan, 2002-2003.
* stderr handling, exec, and beep option added by Morty Abzug, 2008
* Unicode Support added by Jarrod Lowe <procps@rrod.net> in 2009.
*/
#include "c.h"
#include "config.h"
#include "nls.h"
#include "proc/procps.h"
#include "xalloc.h"
#include <ctype.h>
#include <errno.h>
#include <errno.h>
#include <getopt.h>
#include <locale.h>
#include <locale.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <termios.h>
#include <termios.h>
#include <time.h>
#include <unistd.h>
#ifdef WITH_WATCH8BIT
# include <wchar.h>
# include <ncursesw/ncurses.h>
#else
# include <ncurses.h>
#endif /* WITH_WATCH8BIT */
#ifdef FORCE_8BIT
# undef isprint
# define isprint(x) ( (x>=' '&&x<='~') || (x>=0xa0) )
#endif
static int curses_started = 0;
static int height = 24, width = 80;
static int screen_size_changed = 0;
static int first_screen = 1;
static int show_title = 2; /* number of lines used, 2 or 0 */
static int precise_timekeeping = 0;
#define min(x,y) ((x) > (y) ? (y) : (x))
#define MAX_ANSIBUF 10
static void __attribute__ ((__noreturn__))
usage(FILE * out)
{
fputs(USAGE_HEADER, out);
fprintf(out,
_(" %s [options] command\n"), program_invocation_short_name);
fputs(USAGE_OPTIONS, out);
fputs(_(" -b, --beep beep if command has a non-zero exit\n"
" -c, --color interpret ANSI color sequences\n"
" -e, --errexit exit if command has a non-zero exit\n"
" -f, --differences highlight changes between updates\n"
" -n, --interval <secs> seconds to wait between updates\n"
" -p, --precise attempt run command in precise intervals\n"
" -t, --no-title turn off header\n"
" -x, --exec pass command to exec instead of \"sh -c\"\n"
" -h, --help display this help text\n"
" -v, --version display version information and exit\n"), out);
fputs(USAGE_SEPARATOR, out);
fputs(USAGE_HELP, out);
fputs(_(" -v, --version output version information and exit\n"), out);
fprintf(out, USAGE_MAN_TAIL("watch(1)"));
exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
}
static void init_ansi_colors(void)
{
int i;
short ncurses_colors[] = {
COLOR_BLACK, COLOR_RED, COLOR_GREEN, COLOR_YELLOW, COLOR_BLUE,
COLOR_MAGENTA, COLOR_CYAN, COLOR_WHITE
};
for (i = 0; i < 8; i++)
init_pair(i + 1, ncurses_colors[i], -1);
}
static void set_ansi_attribute(const int attrib)
{
switch (attrib) {
case -1:
return;
case 0:
standend();
return;
case 1:
attrset(A_BOLD);
return;
}
if (attrib >= 30 && attrib <= 37) {
color_set(attrib - 29, NULL);
return;
}
}
static void process_ansi(FILE * fp)
{
int i, c, num1, num2;
char buf[MAX_ANSIBUF];
char *nextnum;
c = getc(fp);
if (c != '[') {
ungetc(c, fp);
return;
}
for (i = 0; i < MAX_ANSIBUF; i++) {
c = getc(fp);
/* COLOUR SEQUENCE ENDS in 'm' */
if (c == 'm') {
buf[i] = '\0';
break;
}
if (c < '0' && c > '9' && c != ';') {
while (--i >= 0)
ungetc(buf[i], fp);
return;
}
buf[i] = (char)c;
}
num1 = strtol(buf, &nextnum, 10);
if (nextnum != buf && nextnum[0] != '\0')
num2 = strtol(nextnum + 1, NULL, 10);
else
num2 = -1;
set_ansi_attribute(num1);
set_ansi_attribute(num2);
}
static void __attribute__ ((__noreturn__)) do_exit(int status)
{
if (curses_started)
endwin();
exit(status);
}
/* signal handler */
static void die(int notused __attribute__ ((__unused__)))
{
do_exit(EXIT_SUCCESS);
}
static void winch_handler(int notused __attribute__ ((__unused__)))
{
screen_size_changed = 1;
}
static char env_col_buf[24];
static char env_row_buf[24];
static int incoming_cols;
static int incoming_rows;
static void get_terminal_size(void)
{
struct winsize w;
if (!incoming_cols) {
/* have we checked COLUMNS? */
const char *s = getenv("COLUMNS");
incoming_cols = -1;
if (s && *s) {
long t;
char *endptr;
t = strtol(s, &endptr, 0);
if (!*endptr && (t > 0) && (t < (long)666))
incoming_cols = (int)t;
width = incoming_cols;
snprintf(env_col_buf, sizeof env_col_buf, "COLUMNS=%d",
width);
putenv(env_col_buf);
}
}
if (!incoming_rows) {
/* have we checked LINES? */
const char *s = getenv("LINES");
incoming_rows = -1;
if (s && *s) {
long t;
char *endptr;
t = strtol(s, &endptr, 0);
if (!*endptr && (t > 0) && (t < (long)666))
incoming_rows = (int)t;
height = incoming_rows;
snprintf(env_row_buf, sizeof env_row_buf, "LINES=%d",
height);
putenv(env_row_buf);
}
}
if (incoming_cols < 0 || incoming_rows < 0) {
if (ioctl(2, TIOCGWINSZ, &w) == 0) {
if (incoming_rows < 0 && w.ws_row > 0) {
height = w.ws_row;
snprintf(env_row_buf, sizeof env_row_buf,
"LINES=%d", height);
putenv(env_row_buf);
}
if (incoming_cols < 0 && w.ws_col > 0) {
width = w.ws_col;
snprintf(env_col_buf, sizeof env_col_buf,
"COLUMNS=%d", width);
putenv(env_col_buf);
}
}
}
}
/* get current time in usec */
typedef unsigned long long watch_usec_t;
#define USECS_PER_SEC (1000000ull)
watch_usec_t get_time_usec()
{
struct timeval now;
gettimeofday(&now, NULL);
return USECS_PER_SEC * now.tv_sec + now.tv_usec;
}
#ifdef WITH_WATCH8BIT
/* read a wide character from a popen'd stream */
#define MAX_ENC_BYTES 16
wint_t my_getwc(FILE * s);
wint_t my_getwc(FILE * s)
{
/* assuming no encoding ever consumes more than 16 bytes */
char i[MAX_ENC_BYTES];
int byte = 0;
int convert;
int x;
wchar_t rval;
while (1) {
i[byte] = getc(s);
if (i[byte] == EOF) {
return WEOF;
}
byte++;
errno = 0;
mbtowc(NULL, NULL, 0);
convert = mbtowc(&rval, i, byte);
x = errno;
if (convert > 0) {
/* legal conversion */
return rval;
}
if (byte == MAX_ENC_BYTES) {
while (byte > 1) {
/* at least *try* to fix up */
ungetc(i[--byte], s);
}
errno = -EILSEQ;
return WEOF;
}
}
}
#endif /* WITH_WATCH8BIT */
int main(int argc, char *argv[])
{
int optc;
int option_differences = 0,
option_differences_cumulative = 0,
option_exec = 0,
option_beep = 0,
option_color = 0,
option_errexit = 0, option_help = 0, option_version = 0;
double interval = 2;
char *command;
char **command_argv;
int command_length = 0; /* not including final \0 */
watch_usec_t next_loop; /* next loop time in us, used for precise time
* keeping only */
#ifdef WITH_WATCH8BIT
wchar_t *wcommand = NULL;
int wcommand_columns = 0; /* not including final \0 */
int wcommand_characters = 0; /* not including final \0 */
#endif /* WITH_WATCH8BIT */
int pipefd[2];
int status;
pid_t child;
static struct option longopts[] = {
{"color", no_argument, 0, 'c'},
{"differences", optional_argument, 0, 'd'},
{"help", no_argument, 0, 'h'},
{"interval", required_argument, 0, 'n'},
{"beep", no_argument, 0, 'b'},
{"errexit", no_argument, 0, 'e'},
{"exec", no_argument, 0, 'x'},
{"precise", no_argument, 0, 'p'},
{"no-title", no_argument, 0, 't'},
{"version", no_argument, 0, 'v'},
{0, 0, 0, 0}
};
program_invocation_name = program_invocation_short_name;
setlocale(LC_ALL, "");
bindtextdomain(PACKAGE, LOCALEDIR);
textdomain(PACKAGE);
while ((optc =
getopt_long(argc, argv, "+bced::hn:pvtx", longopts, (int *)0))
!= EOF) {
switch (optc) {
case 'b':
option_beep = 1;
break;
case 'c':
option_color = 1;
break;
case 'd':
option_differences = 1;
if (optarg)
option_differences_cumulative = 1;
break;
case 'e':
option_errexit = 1;
break;
case 't':
show_title = 0;
break;
case 'x':
option_exec = 1;
break;
case 'n':
interval = strtod_or_err(optarg, _("failed to parse argument"));
if (interval < 0.1)
interval = 0.1;
if (interval > ~0u / 1000000)
interval = ~0u / 1000000;
break;
case 'p':
precise_timekeeping = 1;
break;
case 'h':
usage(stdout);
break;
case 'v':
printf(PROCPS_NG_VERSION);
return EXIT_SUCCESS;
default:
usage(stderr);
break;
}
}
if (optind >= argc)
usage(stderr);
/* save for later */
command_argv = &(argv[optind]);
command = xstrdup(argv[optind++]);
command_length = strlen(command);
for (; optind < argc; optind++) {
char *endp;
int s = strlen(argv[optind]);
/* space and \0 */
command = xrealloc(command, command_length + s + 2);
endp = command + command_length;
*endp = ' ';
memcpy(endp + 1, argv[optind], s);
/* space then string length */
command_length += 1 + s;
command[command_length] = '\0';
}
#ifdef WITH_WATCH8BIT
/* convert to wide for printing purposes */
/*mbstowcs(NULL, NULL, 0); */
wcommand_characters = mbstowcs(NULL, command, 0);
if (wcommand_characters < 0) {
fprintf(stderr, _("Unicode Handling Error\n"));
exit(EXIT_FAILURE);
}
wcommand =
(wchar_t *) malloc((wcommand_characters + 1) * sizeof(wcommand));
if (wcommand == NULL) {
fprintf(stderr, _("Unicode Handling Error (malloc)\n"));
exit(EXIT_FAILURE);
}
mbstowcs(wcommand, command, wcommand_characters + 1);
wcommand_columns = wcswidth(wcommand, -1);
#endif /* WITH_WATCH8BIT */
get_terminal_size();
/* Catch keyboard interrupts so we can put tty back in a sane
* state. */
signal(SIGINT, die);
signal(SIGTERM, die);
signal(SIGHUP, die);
signal(SIGWINCH, winch_handler);
/* Set up tty for curses use. */
curses_started = 1;
initscr();
if (option_color) {
if (has_colors()) {
start_color();
use_default_colors();
init_ansi_colors();
} else
option_color = 0;
}
nonl();
noecho();
cbreak();
if (precise_timekeeping)
next_loop = get_time_usec();
for (;;) {
time_t t = time(NULL);
char *ts = ctime(&t);
int tsl = strlen(ts);
char *header;
FILE *p;
int x, y;
int oldeolseen = 1;
if (screen_size_changed) {
get_terminal_size();
resizeterm(height, width);
clear();
/* redrawwin(stdscr); */
screen_size_changed = 0;
first_screen = 1;
}
if (show_title) {
/*
* left justify interval and command, right
* justify time, clipping all to fit window
* width
*/
int hlen = asprintf(&header, _("Every %.1fs: "), interval);
/*
* the rules:
* width < tsl : print nothing
* width < tsl + hlen + 1: print ts
* width = tsl + hlen + 1: print header, ts
* width < tsl + hlen + 4: print header, ..., ts
* width < tsl + hlen + wcommand_columns: print
* header, truncated wcommand,
* ..., ts
* width > "": print header, wcomand, ts
* this is slightly different from how it used to be
*/
if (width >= tsl) {
if (width >= tsl + hlen + 1) {
mvaddstr(0, 0, header);
if (width >= tsl + hlen + 2) {
if (width < tsl + hlen + 4) {
mvaddstr(0,
width - tsl -
4, "... ");
} else {
#ifdef WITH_WATCH8BIT
if (width <
tsl + hlen +
wcommand_columns) {
/* print truncated */
int avail_columns = width - tsl - hlen;
int using_columns = wcommand_columns;
int using_characters = wcommand_characters;
while (using_columns > avail_columns - 4) {
using_characters--;
using_columns
=
wcswidth
(wcommand,
using_characters);
}
mvaddnwstr(0,
hlen,
wcommand,
using_characters);
mvaddstr(0,
width -
tsl -
4,
"... ");
} else {
mvaddwstr(0,
hlen,
wcommand);
}
#else
mvaddnstr(0, hlen,
command,
width - tsl -
hlen);
#endif /* WITH_WATCH8BIT */
}
}
}
mvaddstr(0, width - tsl + 1, ts);
}
free(header);
}
/* allocate pipes */
if (pipe(pipefd) < 0)
err(7, _("pipe"));
/* flush stdout and stderr, since we're about to do fd stuff */
fflush(stdout);
fflush(stderr);
/* fork to prepare to run command */
child = fork();
if (child < 0) { /* fork error */
err(2, _("fork"));
} else if (child == 0) { /* in child */
close(pipefd[0]); /* child doesn't need read side of pipe */
close(1); /* prepare to replace stdout with pipe */
if (dup2(pipefd[1], 1) < 0) { /* replace stdout with write side of pipe */
err(3, _("dup2"));
}
dup2(1, 2); /* stderr should default to stdout */
if (option_exec) { /* pass command to exec instead of system */
if (execvp(command_argv[0], command_argv) == -1) {
err(4, _("exec"));
}
} else {
status = system(command); /* watch manpage promises sh quoting */
/* propagate command exit status as child exit status */
if (!WIFEXITED(status)) { /* child exits nonzero if command does */
exit(EXIT_FAILURE);
} else {
exit(WEXITSTATUS(status));
}
}
}
/* otherwise, we're in parent */
close(pipefd[1]); /* close write side of pipe */
if ((p = fdopen(pipefd[0], "r")) == NULL)
err(5, _("fdopen"));
for (y = show_title; y < height; y++) {
int eolseen = 0, tabpending = 0;
#ifdef WITH_WATCH8BIT
wint_t carry = WEOF;
#endif /* WITH_WATCH8BIT */
for (x = 0; x < width; x++) {
#ifdef WITH_WATCH8BIT
wint_t c = ' ';
#else
int c = ' ';
#endif /* WITH_WATCH8BIT */
int attr = 0;
if (!eolseen) {
/* if there is a tab pending, just
* spit spaces until the next stop
* instead of reading characters */
if (!tabpending)
#ifdef WITH_WATCH8BIT
do {
if (carry == WEOF) {
c = my_getwc(p);
} else {
c = carry;
carry = WEOF;
}
} while (c != WEOF
&& !isprint(c)
&& c < 12
&& wcwidth(c) == 0
&& c != L'\n'
&& c != L'\t'
&& (c != L'\033'
|| option_color !=
1));
#else
do
c = getc(p);
while (c != EOF && !isprint(c)
&& c != '\n'
&& c != '\t'
&& (c != L'\033'
|| option_color !=
1));
#endif /* WITH_WATCH8BIT */
if (c == L'\033' && option_color == 1) {
x--;
process_ansi(p);
continue;
}
if (c == L'\n')
if (!oldeolseen && x == 0) {
x = -1;
continue;
} else
eolseen = 1;
else if (c == L'\t')
tabpending = 1;
#ifdef WITH_WATCH8BIT
if (x == width - 1 && wcwidth(c) == 2) {
y++;
x = -1; /* process this double-width */
carry = c; /* character on the next line */
continue; /* because it won't fit here */
}
if (c == WEOF || c == L'\n'
|| c == L'\t')
c = L' ';
#else
if (c == EOF || c == '\n' || c == '\t')
c = ' ';
#endif /* WITH_WATCH8BIT */
if (tabpending && (((x + 1) % 8) == 0))
tabpending = 0;
}
move(y, x);
if (option_differences) {
#ifdef WITH_WATCH8BIT
cchar_t oldc;
in_wch(&oldc);
attr = !first_screen
&& ((wchar_t) c != oldc.chars[0]
||
(option_differences_cumulative
&& (oldc.
attr & A_ATTRIBUTES)));
#else
chtype oldch = inch();
unsigned char oldc = oldch & A_CHARTEXT;
attr = !first_screen
&& ((unsigned char)c != oldc
||
(option_differences_cumulative
&& (oldch & A_ATTRIBUTES)));
#endif /* WITH_WATCH8BIT */
}
if (attr)
standout();
#ifdef WITH_WATCH8BIT
addnwstr((wchar_t *) & c, 1);
#else
addch(c);
#endif /* WITH_WATCH8BIT */
if (attr)
standend();
#ifdef WITH_WATCH8BIT
if (wcwidth(c) == 0) {
x--;
}
if (wcwidth(c) == 2) {
x++;
}
#endif /* WITH_WATCH8BIT */
}
oldeolseen = eolseen;
}
fclose(p);
/* harvest child process and get status, propagated from command */
if (waitpid(child, &status, 0) < 0)
err(8, _("waitpid"));
/* if child process exited in error, beep if option_beep is set */
if ((!WIFEXITED(status) || WEXITSTATUS(status))) {
if (option_beep)
beep();
if (option_errexit)
exit(8);
}
first_screen = 0;
refresh();
if (precise_timekeeping) {
watch_usec_t cur_time = get_time_usec();
next_loop += USECS_PER_SEC * interval;
if (cur_time < next_loop)
usleep(next_loop - cur_time);
} else
usleep(interval * 1000000);
}
endwin();
return EXIT_SUCCESS;
}