watch.c 20.8 KB
Newer Older
1 2
/*
 * watch -- execute a program repeatedly, displaying output fullscreen
csmall's avatar
csmall committed
3 4 5 6 7
 *
 * Based on the original 1991 'watch' by Tony Rems <[email protected]>
 * (with mods and corrections by Francois Pinard).
 *
 * Substantially reworked, new features (differences option, SIGWINCH
8 9
 * handling, unlimited command length, long line handling) added Apr
 * 1999 by Mike Coleman <[email protected]>.
10
 *
albert's avatar
albert committed
11
 * Changes by Albert Cahalan, 2002-2003.
12
 * stderr handling, exec, and beep option added by Morty Abzug, 2008
Jarrod Lowe's avatar
Jarrod Lowe committed
13
 * Unicode Support added by Jarrod Lowe <[email protected]> in 2009.
14 15 16 17 18 19 20 21 22 23 24 25 26 27
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
csmall's avatar
csmall committed
28 29
 */

Sami Kerola's avatar
Sami Kerola committed
30
#include "c.h"
31
#include "config.h"
32
#include "fileutils.h"
Sami Kerola's avatar
Sami Kerola committed
33
#include "nls.h"
Sami Kerola's avatar
Sami Kerola committed
34
#include "strutils.h"
35
#include "xalloc.h"
csmall's avatar
csmall committed
36
#include <ctype.h>
37
#include <errno.h>
csmall's avatar
csmall committed
38
#include <getopt.h>
39
#include <locale.h>
Craig Small's avatar
Craig Small committed
40
#include <limits.h>
csmall's avatar
csmall committed
41 42 43 44 45
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
46
#include <sys/time.h>
47
#include <sys/wait.h>
48
#include <termios.h>
csmall's avatar
csmall committed
49 50
#include <time.h>
#include <unistd.h>
51
#ifdef WITH_WATCH8BIT
52
# define _XOPEN_SOURCE_EXTENDED 1
Sami Kerola's avatar
Sami Kerola committed
53
# include <wchar.h>
54
# include <wctype.h>
55
#endif	/* WITH_WATCH8BIT */
56
#include <ncurses.h>
csmall's avatar
csmall committed
57

albert's avatar
albert committed
58
#ifdef FORCE_8BIT
Sami Kerola's avatar
Sami Kerola committed
59 60
# undef isprint
# define isprint(x) ( (x>=' '&&x<='~') || (x>=0xa0) )
albert's avatar
albert committed
61 62
#endif

Craig Small's avatar
Craig Small committed
63 64 65 66
#ifndef HOST_NAME_MAX
#define HOST_NAME_MAX 64
#endif

67 68 69 70 71 72 73 74 75 76
/* Boolean command line options */
static int flags;
#define WATCH_DIFF	(1 << 1)
#define WATCH_CUMUL	(1 << 2)
#define WATCH_EXEC	(1 << 3)
#define WATCH_BEEP	(1 << 4)
#define WATCH_COLOR	(1 << 5)
#define WATCH_ERREXIT	(1 << 6)
#define WATCH_CHGEXIT	(1 << 7)

csmall's avatar
csmall committed
77
static int curses_started = 0;
78
static long height = 24, width = 80;
albert's avatar
albert committed
79 80
static int screen_size_changed = 0;
static int first_screen = 1;
81
static int show_title = 2;	/* number of lines used, 2 or 0 */
82
static int precise_timekeeping = 0;
csmall's avatar
csmall committed
83 84

#define min(x,y) ((x) > (y) ? (y) : (x))
85
#define MAX_ANSIBUF 100
86

87 88
static void __attribute__ ((__noreturn__))
    usage(FILE * out)
89
{
Sami Kerola's avatar
Sami Kerola committed
90
	fputs(USAGE_HEADER, out);
91
	fprintf(out,
92
              _(" %s [options] command\n"), program_invocation_short_name);
Sami Kerola's avatar
Sami Kerola committed
93
	fputs(USAGE_OPTIONS, out);
94
	fputs(_("  -b, --beep             beep if command has a non-zero exit\n"), out);
95
	fputs(_("  -c, --color            interpret ANSI color and style sequences\n"), out);
96 97 98 99 100 101 102 103
	fputs(_("  -d, --differences[=<permanent>]\n"
                "                         highlight changes between updates\n"), out);
	fputs(_("  -e, --errexit          exit if command has a non-zero exit\n"), out);
	fputs(_("  -g, --chgexit          exit when output from command changes\n"), out);
	fputs(_("  -n, --interval <secs>  seconds to wait between updates\n"), out);
	fputs(_("  -p, --precise          attempt run command in precise intervals\n"), out);
	fputs(_("  -t, --no-title         turn off header\n"), out);
	fputs(_("  -x, --exec             pass command to exec instead of \"sh -c\"\n"), out);
Sami Kerola's avatar
Sami Kerola committed
104 105 106 107
	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)"));
108 109

	exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
110 111
}

112 113 114 115 116
static int nr_of_colors;
static int attributes;
static int fg_col;
static int bg_col;

117 118 119 120 121 122 123 124

static void reset_ansi(void)
{
	attributes = A_NORMAL;
	fg_col = 0;
	bg_col = 0;
}

125
static void init_ansi_colors(void)
126
{
127
	short ncurses_colors[] = {
128 129
		-1, COLOR_BLACK, COLOR_RED, COLOR_GREEN, COLOR_YELLOW,
		COLOR_BLUE, COLOR_MAGENTA, COLOR_CYAN, COLOR_WHITE
130 131
	};

132 133 134 135 136
	nr_of_colors = sizeof(ncurses_colors) / sizeof(short);

	for (bg_col = 0; bg_col < nr_of_colors; bg_col++)
		for (fg_col = 0; fg_col < nr_of_colors; fg_col++)
			init_pair(bg_col * nr_of_colors + fg_col + 1, ncurses_colors[fg_col], ncurses_colors[bg_col]);
137
	reset_ansi();
138 139
}

140

141
static int set_ansi_attribute(const int attrib)
142
{
143
	switch (attrib) {
144 145 146
	case -1:	/* restore last settings */
		break;
	case 0:		/* restore default settings */
147
		reset_ansi();
148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176
		break;
	case 1:		/* set bold / increased intensity */
		attributes |= A_BOLD;
		break;
	case 2:		/* set decreased intensity (if supported) */
		attributes |= A_DIM;
		break;
#ifdef A_ITALIC
	case 3:		/* set italic (if supported) */
		attributes |= A_ITALIC;
		break;
#endif
	case 4:		/* set underline */
		attributes |= A_UNDERLINE;
		break;
	case 5:		/* set blinking */
		attributes |= A_BLINK;
		break;
	case 7:		/* set inversed */
		attributes |= A_REVERSE;
		break;
	case 21:	/* unset bold / increased intensity */
		attributes &= ~A_BOLD;
		break;
	case 22:	/* unset bold / any intensity modifier */
		attributes &= ~(A_BOLD | A_DIM);
		break;
#ifdef A_ITALIC
	case 23:	/* unset italic */
177
		attributes &= ~A_ITALIC;
178 179 180 181 182 183 184 185 186 187 188 189 190
		break;
#endif
	case 24:	/* unset underline */
		attributes &= ~A_UNDERLINE;
		break;
	case 25:	/* unset blinking */
		attributes &= ~A_BLINK;
		break;
	case 27:	/* unset inversed */
		attributes &= ~A_REVERSE;
		break;
	default:
		if (attrib >= 30 && attrib <= 37) {	/* set foreground color */
191
			fg_col = attrib - 30 + 1;
192
		} else if (attrib >= 40 && attrib <= 47) { /* set background color */
193
			bg_col = attrib - 40 + 1;
194 195
		} else {
			return 0; /* Not understood */
196
		}
197
	}
198
	attrset(attributes | COLOR_PAIR(bg_col * nr_of_colors + fg_col + 1));
Craig Small's avatar
Craig Small committed
199
    return 1;
200
}
csmall's avatar
csmall committed
201

202
static void process_ansi(FILE * fp)
csmall's avatar
csmall committed
203
{
204
	int i, c;
205
	char buf[MAX_ANSIBUF];
206
	char *numstart, *endptr;
207 208 209 210 211 212 213 214 215 216 217 218 219

	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;
		}
220
		if ((c < '0' || c > '9') && c != ';') {
221 222 223 224
			return;
		}
		buf[i] = (char)c;
	}
225 226 227 228 229 230 231
	/*
	 * buf now contains a semicolon-separated list of decimal integers,
	 * each indicating an attribute to apply.
	 * For example, buf might contain "0;1;31", derived from the color
	 * escape sequence "<ESC>[0;1;31m". There can be 1 or more
	 * attributes to apply, but typically there are between 1 and 3.
	 */
232

233 234 235
    /* Special case of <ESC>[m */
    if (buf[0] == '\0')
        set_ansi_attribute(0);
236

237 238
    for (endptr = numstart = buf; *endptr != '\0'; numstart = endptr + 1) {
        if (!set_ansi_attribute(strtol(numstart, &endptr, 10)))
239
            break;
240
    }
csmall's avatar
csmall committed
241 242
}

243
static void __attribute__ ((__noreturn__)) do_exit(int status)
albert's avatar
albert committed
244 245 246 247
{
	if (curses_started)
		endwin();
	exit(status);
csmall's avatar
csmall committed
248 249 250
}

/* signal handler */
251
static void die(int notused __attribute__ ((__unused__)))
csmall's avatar
csmall committed
252
{
253
	do_exit(EXIT_SUCCESS);
csmall's avatar
csmall committed
254 255
}

256
static void winch_handler(int notused __attribute__ ((__unused__)))
csmall's avatar
csmall committed
257
{
albert's avatar
albert committed
258
	screen_size_changed = 1;
csmall's avatar
csmall committed
259 260
}

albert's avatar
albert committed
261 262 263 264 265
static char env_col_buf[24];
static char env_row_buf[24];
static int incoming_cols;
static int incoming_rows;

266
static void get_terminal_size(void)
csmall's avatar
csmall committed
267
{
albert's avatar
albert committed
268
	struct winsize w;
269 270
	if (!incoming_cols) {
		/* have we checked COLUMNS? */
albert's avatar
albert committed
271 272
		const char *s = getenv("COLUMNS");
		incoming_cols = -1;
273
		if (s && *s) {
albert's avatar
albert committed
274 275 276
			long t;
			char *endptr;
			t = strtol(s, &endptr, 0);
277 278
			if (!*endptr && 0 < t)
				incoming_cols = t;
albert's avatar
albert committed
279
			width = incoming_cols;
280
			snprintf(env_col_buf, sizeof env_col_buf, "COLUMNS=%ld",
281
				 width);
albert's avatar
albert committed
282 283 284
			putenv(env_col_buf);
		}
	}
285 286
	if (!incoming_rows) {
		/* have we checked LINES? */
albert's avatar
albert committed
287 288
		const char *s = getenv("LINES");
		incoming_rows = -1;
289
		if (s && *s) {
albert's avatar
albert committed
290 291 292
			long t;
			char *endptr;
			t = strtol(s, &endptr, 0);
293 294
			if (!*endptr && 0 < t)
				incoming_rows = t;
albert's avatar
albert committed
295
			height = incoming_rows;
296
			snprintf(env_row_buf, sizeof env_row_buf, "LINES=%ld",
297
				 height);
albert's avatar
albert committed
298 299 300
			putenv(env_row_buf);
		}
	}
301 302
	if (ioctl(STDERR_FILENO, TIOCGWINSZ, &w) == 0) {
		if (incoming_cols < 0 || incoming_rows < 0) {
303
			if (incoming_rows < 0 && w.ws_row > 0) {
albert's avatar
albert committed
304
				height = w.ws_row;
305
				snprintf(env_row_buf, sizeof env_row_buf,
306
					 "LINES=%ld", height);
albert's avatar
albert committed
307 308
				putenv(env_row_buf);
			}
309
			if (incoming_cols < 0 && w.ws_col > 0) {
albert's avatar
albert committed
310
				width = w.ws_col;
311
				snprintf(env_col_buf, sizeof env_col_buf,
312
					 "COLUMNS=%ld", width);
albert's avatar
albert committed
313 314 315
				putenv(env_col_buf);
			}
		}
albert's avatar
albert committed
316
	}
csmall's avatar
csmall committed
317 318
}

319 320 321
/* get current time in usec */
typedef unsigned long long watch_usec_t;
#define USECS_PER_SEC (1000000ull)
Sami Kerola's avatar
Sami Kerola committed
322
static watch_usec_t get_time_usec()
323
{
324 325
	struct timeval now;
	gettimeofday(&now, NULL);
326
	return USECS_PER_SEC * now.tv_sec + now.tv_usec;
327 328
}

329
#ifdef WITH_WATCH8BIT
330
/* read a wide character from a popen'd stream */
Jarrod Lowe's avatar
Jarrod Lowe committed
331
#define MAX_ENC_BYTES 16
332 333 334 335 336
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];
Jarrod Lowe's avatar
Jarrod Lowe committed
337 338 339 340
	int byte = 0;
	int convert;
	int x;
	wchar_t rval;
341
	while (1) {
Jarrod Lowe's avatar
Jarrod Lowe committed
342
		i[byte] = getc(s);
343 344 345
		if (i[byte] == EOF) {
			return WEOF;
		}
Jarrod Lowe's avatar
Jarrod Lowe committed
346 347 348 349 350
		byte++;
		errno = 0;
		mbtowc(NULL, NULL, 0);
		convert = mbtowc(&rval, i, byte);
		x = errno;
351 352 353 354 355 356 357 358 359 360 361
		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;
Jarrod Lowe's avatar
Jarrod Lowe committed
362 363 364
		}
	}
}
365
#endif	/* WITH_WATCH8BIT */
Jarrod Lowe's avatar
Jarrod Lowe committed
366

367
#ifdef WITH_WATCH8BIT
368
static void output_header(wchar_t *restrict wcommand, int wcommand_characters, double interval)
369
#else
Sami Kerola's avatar
Sami Kerola committed
370
static void output_header(char *restrict command, double interval)
371
#endif	/* WITH_WATCH8BIT */
372 373 374 375
{
	time_t t = time(NULL);
	char *ts = ctime(&t);
	char *header;
376 377
	char *right_header;
	char hostname[HOST_NAME_MAX + 1];
378
	int command_columns = 0;	/* not including final \0 */
379 380

	gethostname(hostname, sizeof(hostname));
381 382

	/*
383
	 * left justify interval and command, right justify hostname and time,
384 385 386
	 * clipping all to fit window width
	 */
	int hlen = asprintf(&header, _("Every %.1fs: "), interval);
387
	int rhlen = asprintf(&right_header, _("%s: %s"), hostname, ts);
388 389 390

	/*
	 * the rules:
391 392 393 394 395 396 397
	 *   width < rhlen : print nothing
	 *   width < rhlen + hlen + 1: print hostname, ts
	 *   width = rhlen + hlen + 1: print header, hostname, ts
	 *   width < rhlen + hlen + 4: print header, ..., hostname, ts
	 *   width < rhlen + hlen + wcommand_columns: print header,
	 *                           truncated wcommand, ..., hostname, ts
	 *   width > "": print header, wcomand, hostname, ts
398 399
	 * this is slightly different from how it used to be
	 */
400
	if (width < rhlen) {
401
		free(header);
402
		free(right_header);
403 404
		return;
	}
405
	if (rhlen + hlen + 1 <= width) {
406
		mvaddstr(0, 0, header);
407 408 409
		if (rhlen + hlen + 2 <= width) {
			if (width < rhlen + hlen + 4) {
				mvaddstr(0, width - rhlen - 4, "... ");
410 411
			} else {
#ifdef WITH_WATCH8BIT
412 413
	            command_columns = wcswidth(wcommand, -1);
				if (width < rhlen + hlen + command_columns) {
414
					/* print truncated */
415
					int available = width - rhlen - hlen;
416
					int in_use = command_columns;
417 418 419 420 421 422
					int wcomm_len = wcommand_characters;
					while (available - 4 < in_use) {
						wcomm_len--;
						in_use = wcswidth(wcommand, wcomm_len);
					}
					mvaddnwstr(0, hlen, wcommand, wcomm_len);
423
					mvaddstr(0, width - rhlen - 4, "... ");
424 425 426 427
				} else {
					mvaddwstr(0, hlen, wcommand);
				}
#else
428 429 430 431 432 433 434 435
                command_columns = strlen(command);
                if (width < rhlen + hlen + command_columns) {
                    /* print truncated */
                    mvaddnstr(0, hlen, command, width - rhlen - hlen - 4);
                    mvaddstr(0, width - rhlen - 4, "... ");
                } else {
                    mvaddnstr(0, hlen, command, width - rhlen - hlen);
                }
436 437 438 439
#endif	/* WITH_WATCH8BIT */
			}
		}
	}
440
	mvaddstr(0, width - rhlen + 1, right_header);
441
	free(header);
442
	free(right_header);
443 444 445
	return;
}

Sami Kerola's avatar
Sami Kerola committed
446
static int run_command(char *restrict command, char **restrict command_argv)
447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474
{
	FILE *p;
	int x, y;
	int oldeolseen = 1;
	int pipefd[2];
	pid_t child;
	int exit_early = 0;
	int status;

	/* allocate pipes */
	if (pipe(pipefd) < 0)
		xerr(7, _("unable to create IPC pipes"));

	/* 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 */
		xerr(2, _("unable to fork process"));
	} 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 */
			xerr(3, _("dup2 failed"));
		}
475
		close(pipefd[1]);		/* once duped, the write fd isn't needed */
476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498
		dup2(1, 2);			/* stderr should default to stdout */

		if (flags & WATCH_EXEC) {	/* pass command to exec instead of system */
			if (execvp(command_argv[0], command_argv) == -1) {
				xerr(4, _("unable to execute '%s'"),
				     command_argv[0]);
			}
		} 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)
		xerr(5, _("fdopen"));

499
	reset_ansi();
500
	for (y = show_title; y < height; y++) {
501
		int eolseen = 0, tabpending = 0, tabwaspending = 0;
502 503
		if (flags & WATCH_COLOR)
			set_ansi_attribute(-1);
504 505 506 507 508 509 510 511 512 513 514
#ifdef WITH_WATCH8BIT
		wint_t carry = WEOF;
#endif
		for (x = 0; x < width; x++) {
#ifdef WITH_WATCH8BIT
			wint_t c = ' ';
#else
			int c = ' ';
#endif
			int attr = 0;

515 516 517 518
			if (tabwaspending && (flags & WATCH_COLOR))
				set_ansi_attribute(-1);
			tabwaspending = 0;

519 520 521 522 523 524 525 526 527 528 529 530 531
			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;
						}
532 533
					} while (c != WEOF && !iswprint(c)
						 && c < 128
534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567
						 && wcwidth(c) == 0
						 && c != L'\n'
						 && c != L'\t'
						 && (c != L'\033'
						     || !(flags & WATCH_COLOR)));
#else
					do
						c = getc(p);
					while (c != EOF && !isprint(c)
					       && c != '\n'
					       && c != '\t'
					       && (c != L'\033'
						   || !(flags & WATCH_COLOR)));
#endif
				if (c == L'\033' && (flags & WATCH_COLOR)) {
					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 */
				}
568
				if (c == WEOF || c == L'\n' || c == L'\t') {
569
					c = L' ';
570 571 572
					if (flags & WATCH_COLOR)
						attrset(A_NORMAL);
				}
573
#else
574
				if (c == EOF || c == '\n' || c == '\t') {
575
					c = ' ';
576 577 578
					if (flags & WATCH_COLOR)
						attrset(A_NORMAL);
				}
579
#endif
580
				if (tabpending && (((x + 1) % 8) == 0)) {
581
					tabpending = 0;
582 583
					tabwaspending = 1;
				}
584 585
			}
			move(y, x);
586

587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639
			if (!first_screen && !exit_early && (flags & WATCH_CHGEXIT)) {
#ifdef WITH_WATCH8BIT
				cchar_t oldc;
				in_wch(&oldc);
				exit_early = (wchar_t) c != oldc.chars[0];
#else
				chtype oldch = inch();
				unsigned char oldc = oldch & A_CHARTEXT;
				exit_early = (unsigned char)c != oldc;
#endif
			}
			if (flags & WATCH_DIFF) {
#ifdef WITH_WATCH8BIT
				cchar_t oldc;
				in_wch(&oldc);
				attr = !first_screen
				    && ((wchar_t) c != oldc.chars[0]
					||
					((flags & WATCH_CUMUL)
					 && (oldc.attr & A_ATTRIBUTES)));
#else
				chtype oldch = inch();
				unsigned char oldc = oldch & A_CHARTEXT;
				attr = !first_screen
				    && ((unsigned char)c != oldc
					||
					((flags & WATCH_CUMUL)
					 && (oldch & A_ATTRIBUTES)));
#endif
			}
			if (attr)
				standout();
#ifdef WITH_WATCH8BIT
			addnwstr((wchar_t *) & c, 1);
#else
			addch(c);
#endif
			if (attr)
				standend();
#ifdef WITH_WATCH8BIT
			if (wcwidth(c) == 0) {
				x--;
			}
			if (wcwidth(c) == 2) {
				x++;
			}
#endif
		}
		oldeolseen = eolseen;
	}

	fclose(p);

640

641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662
	/* harvest child process and get status, propagated from command */
	if (waitpid(child, &status, 0) < 0)
		xerr(8, _("waitpid"));

	/* if child process exited in error, beep if option_beep is set */
	if ((!WIFEXITED(status) || WEXITSTATUS(status))) {
		if (flags & WATCH_BEEP)
			beep();
		if (flags & WATCH_ERREXIT) {
			mvaddstr(height - 1, 0,
				 _("command exit with a non-zero status, press a key to exit"));
			refresh();
			fgetc(stdin);
			endwin();
			exit(8);
		}
	}
	first_screen = 0;
	refresh();
	return exit_early;
}

663
int main(int argc, char *argv[])
csmall's avatar
csmall committed
664
{
albert's avatar
albert committed
665
	int optc;
666
	double interval = 2;
albert's avatar
albert committed
667
	char *command;
668
	char **command_argv;
albert's avatar
albert committed
669
	int command_length = 0;	/* not including final \0 */
670 671
	watch_usec_t next_loop;	/* next loop time in us, used for precise time
				 * keeping only */
672 673
#ifdef WITH_WATCH8BIT
	wchar_t *wcommand = NULL;
674 675
	int wcommand_characters = 0;	/* not including final \0 */
#endif	/* WITH_WATCH8BIT */
676

677 678 679 680 681 682 683
	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'},
684
		{"chgexit", no_argument, 0, 'g'},
685 686 687 688 689 690 691
		{"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}
	};

692
#ifdef HAVE_PROGRAM_INVOCATION_NAME
693
	program_invocation_name = program_invocation_short_name;
694
#endif
albert's avatar
albert committed
695
	setlocale(LC_ALL, "");
696 697
	bindtextdomain(PACKAGE, LOCALEDIR);
	textdomain(PACKAGE);
698
	atexit(close_stdout);
albert's avatar
albert committed
699

700
	while ((optc =
701
		getopt_long(argc, argv, "+bced::ghn:pvtx", longopts, (int *)0))
albert's avatar
albert committed
702 703
	       != EOF) {
		switch (optc) {
704
		case 'b':
705
			flags |= WATCH_BEEP;
706
			break;
707
		case 'c':
708
			flags |= WATCH_COLOR;
709
			break;
albert's avatar
albert committed
710
		case 'd':
711
			flags |= WATCH_DIFF;
albert's avatar
albert committed
712
			if (optarg)
713
				flags |= WATCH_CUMUL;
albert's avatar
albert committed
714
			break;
715
		case 'e':
716
			flags |= WATCH_ERREXIT;
albert's avatar
albert committed
717
			break;
718
		case 'g':
719
			flags |= WATCH_CHGEXIT;
720
			break;
albert's avatar
albert committed
721 722 723
		case 't':
			show_title = 0;
			break;
724
		case 'x':
725
			flags |= WATCH_EXEC;
726
			break;
albert's avatar
albert committed
727
		case 'n':
728
			interval = strtod_nol_or_err(optarg, _("failed to parse argument"));
729 730
			if (interval < 0.1)
				interval = 0.1;
Craig Small's avatar
Craig Small committed
731 732
			if (interval > UINT_MAX)
				interval = UINT_MAX;
albert's avatar
albert committed
733
			break;
734 735 736
		case 'p':
			precise_timekeeping = 1;
			break;
737 738
		case 'h':
			usage(stdout);
albert's avatar
albert committed
739
			break;
740
		case 'v':
Sami Kerola's avatar
Sami Kerola committed
741
			printf(PROCPS_NG_VERSION);
742
			return EXIT_SUCCESS;
albert's avatar
albert committed
743
		default:
744
			usage(stderr);
albert's avatar
albert committed
745 746
			break;
		}
csmall's avatar
csmall committed
747
	}
albert's avatar
albert committed
748 749

	if (optind >= argc)
750
		usage(stderr);
albert's avatar
albert committed
751

752 753
	/* save for later */
	command_argv = &(argv[optind]);
754

755
	command = xstrdup(argv[optind++]);
albert's avatar
albert committed
756 757 758 759
	command_length = strlen(command);
	for (; optind < argc; optind++) {
		char *endp;
		int s = strlen(argv[optind]);
760
		/* space and \0 */
761
		command = xrealloc(command, command_length + s + 2);
albert's avatar
albert committed
762 763 764
		endp = command + command_length;
		*endp = ' ';
		memcpy(endp + 1, argv[optind], s);
765 766
		/* space then string length */
		command_length += 1 + s;
albert's avatar
albert committed
767
		command[command_length] = '\0';
csmall's avatar
csmall committed
768 769
	}

770
#ifdef WITH_WATCH8BIT
771 772
	/* convert to wide for printing purposes */
	/*mbstowcs(NULL, NULL, 0); */
Jarrod Lowe's avatar
Jarrod Lowe committed
773
	wcommand_characters = mbstowcs(NULL, command, 0);
774
	if (wcommand_characters < 0) {
775
		fprintf(stderr, _("unicode handling error\n"));
776
		exit(EXIT_FAILURE);
Jarrod Lowe's avatar
Jarrod Lowe committed
777
	}
778 779 780
	wcommand =
	    (wchar_t *) malloc((wcommand_characters + 1) * sizeof(wcommand));
	if (wcommand == NULL) {
781
		fprintf(stderr, _("unicode handling error (malloc)\n"));
782
		exit(EXIT_FAILURE);
Jarrod Lowe's avatar
Jarrod Lowe committed
783
	}
784 785
	mbstowcs(wcommand, command, wcommand_characters + 1);
#endif	/* WITH_WATCH8BIT */
Jarrod Lowe's avatar
Jarrod Lowe committed
786

albert's avatar
albert committed
787 788
	get_terminal_size();

789 790
	/* Catch keyboard interrupts so we can put tty back in a sane
	 * state.  */
albert's avatar
albert committed
791 792 793 794 795 796 797 798
	signal(SIGINT, die);
	signal(SIGTERM, die);
	signal(SIGHUP, die);
	signal(SIGWINCH, winch_handler);

	/* Set up tty for curses use.  */
	curses_started = 1;
	initscr();
799
	if (flags & WATCH_COLOR) {
800 801 802 803
		if (has_colors()) {
			start_color();
			use_default_colors();
			init_ansi_colors();
804 805 806
		} else {
			flags |= WATCH_COLOR;
		}
807
	}
albert's avatar
albert committed
808 809 810 811
	nonl();
	noecho();
	cbreak();

812 813 814
	if (precise_timekeeping)
		next_loop = get_time_usec();

815
	while (1) {
albert's avatar
albert committed
816 817 818 819 820 821 822
		if (screen_size_changed) {
			get_terminal_size();
			resizeterm(height, width);
			clear();
			/* redrawwin(stdscr); */
			screen_size_changed = 0;
			first_screen = 1;
csmall's avatar
csmall committed
823
		}
albert's avatar
albert committed
824

825
		if (show_title)
826
#ifdef WITH_WATCH8BIT
Craig Small's avatar
Craig Small committed
827
			output_header(wcommand, wcommand_characters, interval);
828
#else
829
			output_header(command, interval);
830
#endif	/* WITH_WATCH8BIT */
csmall's avatar
csmall committed
831

832 833
		if (run_command(command, command_argv))
			break;
834 835


836 837
		if (precise_timekeeping) {
			watch_usec_t cur_time = get_time_usec();
838
			next_loop += USECS_PER_SEC * interval;
839 840 841
			if (cur_time < next_loop)
				usleep(next_loop - cur_time);
		} else
Craig Small's avatar
Craig Small committed
842 843 844 845
			if (interval < UINT_MAX / USECS_PER_SEC)
				usleep(interval * USECS_PER_SEC);
			else
				sleep(interval);
846
	}
csmall's avatar
csmall committed
847

848 849
	endwin();
	return EXIT_SUCCESS;
csmall's avatar
csmall committed
850
}