diff options
Diffstat (limited to '')
-rw-r--r-- | src/watch.c | 1045 |
1 files changed, 1045 insertions, 0 deletions
diff --git a/src/watch.c b/src/watch.c new file mode 100644 index 0000000..5c159a9 --- /dev/null +++ b/src/watch.c @@ -0,0 +1,1045 @@ +/* + * watch - execute a program repeatedly, displaying output fullscreen + * + * Copyright © 2010-2023 Jim Warner <james.warner@comcast.net> + * Copyright © 2015-2023 Craig Small <csmall@dropbear.xyz> + * Copyright © 2011-2012 Sami Kerola <kerolasa@iki.fi> + * Copyright © 2002-2007 Albert Cahalan + * Copyright © 1999 Mike Coleman <mkc@acm.org>. + * + * Based on the original 1991 'watch' by Tony Rems <rembo@unisoft.com> + * (with mods and corrections by Francois Pinard). + * + * stderr handling, exec, and beep option added by Morty Abzug, 2008 + * Unicode Support added by Jarrod Lowe <procps@rrod.net> in 2009. + * + * 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 + */ + +#include "c.h" +#include "config.h" +#include "fileutils.h" +#include "nls.h" +#include "strutils.h" +#include "xalloc.h" +#include <ctype.h> +#include <errno.h> +#include <getopt.h> +#include <locale.h> +#include <limits.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sys/time.h> +#include <sys/wait.h> +#include <termios.h> +#include <time.h> +#include <unistd.h> +#ifdef WITH_WATCH8BIT +# define _XOPEN_SOURCE_EXTENDED 1 +# include <wchar.h> +# include <wctype.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 + +/* 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) +#define WATCH_EQUEXIT (1 << 8) +#define WATCH_NORERUN (1 << 9) + +static int curses_started = 0; +static long 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; +static int line_wrap = 1; + +#define min(x,y) ((x) > (y) ? (y) : (x)) +#define MAX_ANSIBUF 100 + +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"), out); + fputs(_(" -c, --color interpret ANSI color and style sequences\n"), out); + fputs(_(" -C, --no-color do not interpret ANSI color and style sequences\n"), out); + 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(_(" -q, --equexit <cycles>\n" + " exit when output from command does not change\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(_(" -r, --no-rerun do not rerun program on window resize\n"), out); + fputs(_(" -t, --no-title turn off header\n"), out); + fputs(_(" -w, --no-wrap turn off line wrapping\n"), out); + fputs(_(" -x, --exec pass command to exec instead of \"sh -c\"\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 int nr_of_colors; +static int attributes; +static int fg_col; +static int bg_col; +static int more_colors; + + +static void reset_ansi(void) +{ + attributes = A_NORMAL; + fg_col = 0; + bg_col = 0; +} + +static void init_ansi_colors(void) +{ + short ncurses_colors[] = { + -1, COLOR_BLACK, COLOR_RED, COLOR_GREEN, COLOR_YELLOW, + COLOR_BLUE, COLOR_MAGENTA, COLOR_CYAN, COLOR_WHITE + }; + nr_of_colors = sizeof(ncurses_colors) / sizeof(short); + + more_colors = (COLORS >= 16) && (COLOR_PAIRS >= 16 * 16); + + // Initialize ncurses colors. -1 is terminal default + // 0-7 are auto created standard colors initialized by ncurses + if (more_colors) { + // Initialize using ANSI SGR 8-bit specified colors + // 8-15 are bright colors + init_color(8, 333, 333, 333); // Bright black + init_color(9, 1000, 333, 333); // Bright red + init_color(10, 333, 1000, 333); // Bright green + init_color(11, 1000, 1000, 333); // Bright yellow + init_color(12, 333, 333, 1000); // Bright blue + init_color(13, 1000, 333, 1000); // Bright magenta + init_color(14, 333, 1000, 1000); // Bright cyan + // Often ncurses is built with only 256 colors, so lets + // stop here - so we can support the -1 terminal default + //init_color(15, 1000, 1000, 1000); // Bright white + nr_of_colors += 7; + } + + // Initialize all color pairs with ncurses + 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, fg_col - 1, bg_col - 1); + + reset_ansi(); +} + + +static int process_ansi_color_escape_sequence(char** escape_sequence) { + // process SGR ANSI color escape sequence + // Eg 8-bit + // 38;5;⟨n⟩ (set fg color to n) + // 48;5;⟨n⟩ (set bg color to n) + // + // Eg 24-bit (not yet implemented) + // ESC[ 38;2;⟨r⟩;⟨g⟩;⟨b⟩ m Select RGB foreground color + // ESC[ 48;2;⟨r⟩;⟨g⟩;⟨b⟩ m Select RGB background color + int num; + + if (!escape_sequence) + return 0; /* avoid NULLPTR dereference, return "not understood" */ + + if ((*escape_sequence)[0] != ';') + return 0; /* not understood */ + + if ((*escape_sequence)[1] == '5') { + // 8 bit! ANSI specifies a predefined set of 256 colors here. + if ((*escape_sequence)[2] != ';') + return 0; /* not understood */ + num = strtol((*escape_sequence) + 3, escape_sequence, 10); + if (num >= 0 && num <= 7) { + // 0-7 are standard colors same as SGR 30-37 + return num + 1; + } + if (num >= 8 && num <= 15) { + // 8-15 are standard colors same as SGR 90-97 + return more_colors ? num + 1 : num - 8 + 1; + } + + // Remainder aren't yet implemented + // 16-231: 6 × 6 × 6 cube (216 colors): 16 + 36 × r + 6 × g + b (0 ≤ r, g, b ≤ 5) + // 232-255: grayscale from black to white in 24 steps + } + + return 0; /* not understood */ +} + + +static int set_ansi_attribute(const int attrib, char** escape_sequence) +{ + switch (attrib) { + case -1: /* restore last settings */ + break; + case 0: /* restore default settings */ + reset_ansi(); + 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 */ + attributes &= ~A_ITALIC; + 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; + case 38: + fg_col = process_ansi_color_escape_sequence(escape_sequence); + if (fg_col == 0) { + return 0; /* not understood */ + } + break; + case 39: + fg_col = 0; + break; + case 48: + bg_col = process_ansi_color_escape_sequence(escape_sequence); + if (bg_col == 0) { + return 0; /* not understood */ + } + break; + case 49: + bg_col = 0; + break; + default: + if (attrib >= 30 && attrib <= 37) { /* set foreground color */ + fg_col = attrib - 30 + 1; + } else if (attrib >= 40 && attrib <= 47) { /* set background color */ + bg_col = attrib - 40 + 1; + } else if (attrib >= 90 && attrib <= 97) { /* set bright fg color */ + fg_col = more_colors ? attrib - 90 + 9 : attrib - 90 + 1; + } else if (attrib >= 100 && attrib <= 107) { /* set bright bg color */ + bg_col = more_colors ? attrib - 100 + 9 : attrib - 100 + 1; + } else { + return 0; /* Not understood */ + } + } + attr_set(attributes, bg_col * nr_of_colors + fg_col + 1, NULL); + return 1; +} + +static void process_ansi(FILE * fp) +{ + int i, c; + char buf[MAX_ANSIBUF]; + char *numstart, *endptr = buf; + int ansi_attribute; + + c = getc(fp); + + if (c == '(') { + c = getc(fp); + 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 != ';') { + return; + } + buf[i] = (char)c; + } + /* + * 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. + */ + + /* Special case of <ESC>[m */ + if (buf[0] == '\0') + set_ansi_attribute(0, NULL); + + for (endptr = numstart = buf; *endptr != '\0'; numstart = endptr + 1) { + ansi_attribute = strtol(numstart, &endptr, 10); + if (!set_ansi_attribute(ansi_attribute, &endptr)) + break; + if (numstart == endptr) + set_ansi_attribute(0, NULL); /* [m treated as [0m */ + } +} + +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 && 0 < t) + incoming_cols = t; + width = incoming_cols; + snprintf(env_col_buf, sizeof env_col_buf, "COLUMNS=%ld", + 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 && 0 < t) + incoming_rows = t; + height = incoming_rows; + snprintf(env_row_buf, sizeof env_row_buf, "LINES=%ld", + height); + putenv(env_row_buf); + } + } + if (ioctl(STDERR_FILENO, TIOCGWINSZ, &w) == 0) { + if (incoming_cols < 0 || incoming_rows < 0) { + if (incoming_rows < 0 && w.ws_row > 0) { + height = w.ws_row; + snprintf(env_row_buf, sizeof env_row_buf, + "LINES=%ld", 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=%ld", width); + putenv(env_col_buf); + } + } + } +} + +/* get current time in usec */ +typedef unsigned long long watch_usec_t; +#define USECS_PER_SEC (1000000ull) +static 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; + 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); + 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 */ + +#ifdef WITH_WATCH8BIT +static void output_header(wchar_t *restrict wcommand, int wcommand_characters, double interval) +#else +static void output_header(char *restrict command, double interval) +#endif /* WITH_WATCH8BIT */ +{ + time_t t = time(NULL); + char *ts = ctime(&t); + char *header; + char *right_header; + int max_host_name_len = (int) sysconf(_SC_HOST_NAME_MAX); + char hostname[max_host_name_len + 1]; + int command_columns = 0; /* not including final \0 */ + + gethostname(hostname, sizeof(hostname)); + + /* + * left justify interval and command, right justify hostname and time, + * clipping all to fit window width + */ + int hlen = asprintf(&header, _("Every %.1fs: "), interval); + int rhlen = asprintf(&right_header, _("%s: %s"), hostname, ts); + + /* + * the rules: + * 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 + * this is slightly different from how it used to be + */ + if (width < rhlen) { + free(header); + free(right_header); + return; + } + if (rhlen + hlen + 1 <= width) { + mvaddstr(0, 0, header); + if (rhlen + hlen + 2 <= width) { + if (width < rhlen + hlen + 4) { + mvaddstr(0, width - rhlen - 4, "... "); + } else { +#ifdef WITH_WATCH8BIT + command_columns = wcswidth(wcommand, -1); + if (width < rhlen + hlen + command_columns) { + /* print truncated */ + int available = width - rhlen - hlen; + int in_use = command_columns; + int wcomm_len = wcommand_characters; + while (available - 4 < in_use) { + wcomm_len--; + in_use = wcswidth(wcommand, wcomm_len); + } + mvaddnwstr(0, hlen, wcommand, wcomm_len); + mvaddstr(0, width - rhlen - 4, "... "); + } else { + mvaddwstr(0, hlen, wcommand); + } +#else + 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); + } +#endif /* WITH_WATCH8BIT */ + } + } + } + mvaddstr(0, width - rhlen + 1, right_header); + free(header); + free(right_header); + return; +} + +static void find_eol(FILE *p) +{ + int c; +#ifdef WITH_WATCH8BIT + do { + c = my_getwc(p); + } while (c != WEOF + && c!= L'\n'); +#else + do { + c = getc(p); + } while (c != EOF + && c != '\n'); +#endif /* WITH_WATCH8BIT */ +} + +static int run_command(char *restrict command, char **restrict command_argv) +{ + FILE *p; + int x, y; + int oldeolseen = 1; + int pipefd[2]; + pid_t child; + int exit_early = 0; + int buffer_size = 0; + int unchanged_buffer = 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")); + } + close(pipefd[1]); /* once duped, the write fd isn't needed */ + 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")); + + reset_ansi(); + for (y = show_title; y < height; y++) { + int eolseen = 0, tabpending = 0, tabwaspending = 0; + if (flags & WATCH_COLOR) + set_ansi_attribute(-1, NULL); +#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; + + if (tabwaspending && (flags & WATCH_COLOR)) + set_ansi_attribute(-1, NULL); + tabwaspending = 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 && !iswprint(c) + && c < 128 + && wcwidth(c) == 0 + && c != L'\a' + && c != L'\n' + && c != L'\t' + && (c != L'\033' + || !(flags & WATCH_COLOR))); +#else + do + c = getc(p); + while (c != EOF && !isprint(c) + && c != '\a' + && 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; + else if (c == L'\a') { + beep(); + continue; + } +#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' '; + if (flags & WATCH_COLOR) + attrset(A_NORMAL); + } +#else + if (c == EOF || c == '\n' || c == '\t') { + c = ' '; + if (flags & WATCH_COLOR) + attrset(A_NORMAL); + } +#endif + if (tabpending && (((x + 1) % 8) == 0)) { + tabpending = 0; + tabwaspending = 1; + } + } + move(y, x); + + 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 (!first_screen && !exit_early && (flags & WATCH_EQUEXIT)) { + buffer_size++; +#ifdef WITH_WATCH8BIT + cchar_t oldc; + in_wch(&oldc); + if ((wchar_t) c == oldc.chars[0]) + unchanged_buffer++; +#else + chtype oldch = inch(); + unsigned char oldc = oldch & A_CHARTEXT; + if ((unsigned char)c == oldc) + unchanged_buffer++; +#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; + if (!line_wrap) { + reset_ansi(); + if (flags & WATCH_COLOR) + attrset(A_NORMAL); + } + if (!line_wrap && !eolseen) + { + find_eol(p); + } + } + + fclose(p); + + /* 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); + } + } + + if (unchanged_buffer == buffer_size && (flags & WATCH_EQUEXIT)) + exit_early = 1; + + first_screen = 0; + refresh(); + return exit_early; +} + +int main(int argc, char *argv[]) +{ + int optc; + double interval = 2; + int max_cycles = 1; + int cycle_count = 0; + char *interval_string; + char *command; + char **command_argv; + int command_length = 0; /* not including final \0 */ + watch_usec_t last_run = 0; + watch_usec_t next_loop = 0; /* next loop time in us, used for precise time + * keeping only */ +#ifdef WITH_WATCH8BIT + wchar_t *wcommand = NULL; + int wcommand_characters = 0; /* not including final \0 */ +#endif /* WITH_WATCH8BIT */ + +#ifdef WITH_COLORWATCH + flags |= WATCH_COLOR; +#endif /* WITH_COLORWATCH */ + + static struct option longopts[] = { + {"color", no_argument, 0, 'c'}, + {"no-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'}, + {"chgexit", no_argument, 0, 'g'}, + {"equexit", required_argument, 0, 'q'}, + {"exec", no_argument, 0, 'x'}, + {"precise", no_argument, 0, 'p'}, + {"no-rerun", no_argument, 0, 'r'}, + {"no-title", no_argument, 0, 't'}, + {"no-wrap", no_argument, 0, 'w'}, + {"version", no_argument, 0, 'v'}, + {0, 0, 0, 0} + }; + +#ifdef HAVE_PROGRAM_INVOCATION_NAME + program_invocation_name = program_invocation_short_name; +#endif + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + atexit(close_stdout); + + interval_string = getenv("WATCH_INTERVAL"); + if(interval_string != NULL) + interval = strtod_nol_or_err(interval_string, _("Could not parse interval from WATCH_INTERVAL")); + + while ((optc = + getopt_long(argc, argv, "+bCced::ghq:n:prtwvx", longopts, (int *)0)) + != EOF) { + switch (optc) { + case 'b': + flags |= WATCH_BEEP; + break; + case 'c': + flags |= WATCH_COLOR; + break; + case 'C': + flags &= ~WATCH_COLOR; + break; + case 'd': + flags |= WATCH_DIFF; + if (optarg) + flags |= WATCH_CUMUL; + break; + case 'e': + flags |= WATCH_ERREXIT; + break; + case 'g': + flags |= WATCH_CHGEXIT; + break; + case 'q': + flags |= WATCH_EQUEXIT; + max_cycles = strtod_nol_or_err(optarg, _("failed to parse argument")); + break; + case 'r': + flags |= WATCH_NORERUN; + break; + case 't': + show_title = 0; + break; + case 'w': + line_wrap = 0; + break; + case 'x': + flags |= WATCH_EXEC; + break; + case 'n': + interval = strtod_nol_or_err(optarg, _("failed to parse argument")); + 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 (interval < 0.1) + interval = 0.1; + if (interval > UINT_MAX) + interval = UINT_MAX; + + 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); +#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 (flags & WATCH_COLOR) { + if (has_colors()) { + start_color(); + use_default_colors(); + init_ansi_colors(); + } else { + flags &= ~WATCH_COLOR; + } + } + nonl(); + noecho(); + cbreak(); + + if (precise_timekeeping) + next_loop = get_time_usec(); + + while (1) { + if (screen_size_changed) { + get_terminal_size(); + resizeterm(height, width); + clear(); + /* redrawwin(stdscr); */ + screen_size_changed = 0; + first_screen = 1; + } + + if (show_title) +#ifdef WITH_WATCH8BIT + output_header(wcommand, wcommand_characters, interval); +#else + output_header(command, interval); +#endif /* WITH_WATCH8BIT */ + + if (!(flags & WATCH_NORERUN) || + get_time_usec() - last_run > interval * USECS_PER_SEC) { + last_run = get_time_usec(); + int exit = run_command(command, command_argv); + + if (flags & WATCH_EQUEXIT) { + if (cycle_count == max_cycles && exit) { + break; + } else if (exit) { + cycle_count++; + } else { + cycle_count = 0; + } + } else if (exit) { + break; + } + } else { + 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 + if (interval < UINT_MAX / USECS_PER_SEC) + usleep(interval * USECS_PER_SEC); + else + sleep(interval); + } + + endwin(); + return EXIT_SUCCESS; +} |