Bump to procps-ng 3.3.17
[platform/upstream/procps-ng.git] / watch.c
1 /*
2  * watch -- execute a program repeatedly, displaying output fullscreen
3  *
4  * Based on the original 1991 'watch' by Tony Rems <rembo@unisoft.com>
5  * (with mods and corrections by Francois Pinard).
6  *
7  * Substantially reworked, new features (differences option, SIGWINCH
8  * handling, unlimited command length, long line handling) added Apr
9  * 1999 by Mike Coleman <mkc@acm.org>.
10  *
11  * Changes by Albert Cahalan, 2002-2003.
12  * stderr handling, exec, and beep option added by Morty Abzug, 2008
13  * Unicode Support added by Jarrod Lowe <procps@rrod.net> in 2009.
14  *
15  * This library is free software; you can redistribute it and/or
16  * modify it under the terms of the GNU Lesser General Public
17  * License as published by the Free Software Foundation; either
18  * version 2.1 of the License, or (at your option) any later version.
19  *
20  * This library is distributed in the hope that it will be useful,
21  * but WITHOUT ANY WARRANTY; without even the implied warranty of
22  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
23  * Lesser General Public License for more details.
24  *
25  * You should have received a copy of the GNU Lesser General Public
26  * License along with this library; if not, write to the Free Software
27  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
28  */
29
30 #include "c.h"
31 #include "config.h"
32 #include "fileutils.h"
33 #include "nls.h"
34 #include "strutils.h"
35 #include "xalloc.h"
36 #include <ctype.h>
37 #include <errno.h>
38 #include <getopt.h>
39 #include <locale.h>
40 #include <limits.h>
41 #include <signal.h>
42 #include <stdio.h>
43 #include <stdlib.h>
44 #include <string.h>
45 #include <sys/ioctl.h>
46 #include <sys/time.h>
47 #include <sys/wait.h>
48 #include <termios.h>
49 #include <time.h>
50 #include <unistd.h>
51 #ifdef WITH_WATCH8BIT
52 # define _XOPEN_SOURCE_EXTENDED 1
53 # include <wchar.h>
54 # include <wctype.h>
55 #endif  /* WITH_WATCH8BIT */
56 #include <ncurses.h>
57
58 #ifdef FORCE_8BIT
59 # undef isprint
60 # define isprint(x) ( (x>=' '&&x<='~') || (x>=0xa0) )
61 #endif
62
63
64 /* Boolean command line options */
65 static int flags;
66 #define WATCH_DIFF      (1 << 1)
67 #define WATCH_CUMUL     (1 << 2)
68 #define WATCH_EXEC      (1 << 3)
69 #define WATCH_BEEP      (1 << 4)
70 #define WATCH_COLOR     (1 << 5)
71 #define WATCH_ERREXIT   (1 << 6)
72 #define WATCH_CHGEXIT   (1 << 7)
73
74 static int curses_started = 0;
75 static long height = 24, width = 80;
76 static int screen_size_changed = 0;
77 static int first_screen = 1;
78 static int show_title = 2;      /* number of lines used, 2 or 0 */
79 static int precise_timekeeping = 0;
80 static int line_wrap = 1;
81
82 #define min(x,y) ((x) > (y) ? (y) : (x))
83 #define MAX_ANSIBUF 100
84
85 static void __attribute__ ((__noreturn__))
86         usage(FILE * out)
87 {
88         fputs(USAGE_HEADER, out);
89         fprintf(out,
90               _(" %s [options] command\n"), program_invocation_short_name);
91         fputs(USAGE_OPTIONS, out);
92         fputs(_("  -b, --beep             beep if command has a non-zero exit\n"), out);
93         fputs(_("  -c, --color            interpret ANSI color and style sequences\n"), out);
94         fputs(_("  -d, --differences[=<permanent>]\n"
95                 "                         highlight changes between updates\n"), out);
96         fputs(_("  -e, --errexit          exit if command has a non-zero exit\n"), out);
97         fputs(_("  -g, --chgexit          exit when output from command changes\n"), out);
98         fputs(_("  -n, --interval <secs>  seconds to wait between updates\n"), out);
99         fputs(_("  -p, --precise          attempt run command in precise intervals\n"), out);
100         fputs(_("  -t, --no-title         turn off header\n"), out);
101         fputs(_("  -w, --no-wrap          turn off line wrapping\n"), out);
102         fputs(_("  -x, --exec             pass command to exec instead of \"sh -c\"\n"), out);
103         fputs(USAGE_SEPARATOR, out);
104         fputs(USAGE_HELP, out);
105         fputs(_(" -v, --version  output version information and exit\n"), out);
106         fprintf(out, USAGE_MAN_TAIL("watch(1)"));
107
108         exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
109 }
110
111 static int nr_of_colors;
112 static int attributes;
113 static int fg_col;
114 static int bg_col;
115 static int more_colors;
116
117
118 static void reset_ansi(void)
119 {
120         attributes = A_NORMAL;
121         fg_col = 0;
122         bg_col = 0;
123 }
124
125 static void init_ansi_colors(void)
126 {
127         int color;
128
129         short ncurses_colors[] = {
130                 -1, COLOR_BLACK, COLOR_RED, COLOR_GREEN, COLOR_YELLOW,
131                 COLOR_BLUE, COLOR_MAGENTA, COLOR_CYAN, COLOR_WHITE
132         };
133         nr_of_colors = sizeof(ncurses_colors) / sizeof(short);
134
135         more_colors = (COLORS >= 16) && (COLOR_PAIRS >= 16 * 16);
136
137         // Initialize ncurses colors. -1 is terminal default
138         // 0-7 are auto created standard colors initialized by ncurses
139         if (more_colors) {
140                 // Initialize using ANSI SGR 8-bit specified colors
141                 // 8-15 are bright colors
142                 init_color(8, 333, 333, 333);  // Bright black
143                 init_color(9, 1000, 333, 333);  // Bright red
144                 init_color(10, 333, 1000, 333);  // Bright green
145                 init_color(11, 1000, 1000, 333);  // Bright yellow
146                 init_color(12, 333, 333, 1000);  // Bright blue
147                 init_color(13, 1000, 333, 1000);  // Bright magenta
148                 init_color(14, 333, 1000, 1000);  // Bright cyan
149                 // Often ncurses is built with only 256 colors, so lets
150                 // stop here - so we can support the -1 terminal default
151                 //init_color(15, 1000, 1000, 1000);  // Bright white
152                 nr_of_colors += 7;
153         }
154
155         // Initialize all color pairs with ncurses
156         for (bg_col = 0; bg_col < nr_of_colors; bg_col++)
157                 for (fg_col = 0; fg_col < nr_of_colors; fg_col++)
158                         init_pair(bg_col * nr_of_colors + fg_col + 1, fg_col - 1, bg_col - 1);
159
160         reset_ansi();
161 }
162
163
164 static int process_ansi_color_escape_sequence(char** escape_sequence) {
165         // process SGR ANSI color escape sequence
166         // Eg 8-bit
167         // 38;5;⟨n⟩  (set fg color to n)
168         // 48;5;⟨n⟩  (set bg color to n)
169         //
170         // Eg 24-bit (not yet implemented)
171         // ESC[ 38;2;⟨r⟩;⟨g⟩;⟨b⟩ m Select RGB foreground color
172         // ESC[ 48;2;⟨r⟩;⟨g⟩;⟨b⟩ m Select RGB background color
173         int num;
174
175         if ((*escape_sequence)[0] != ';')
176                 return 0; /* not understood */
177
178         if ((*escape_sequence)[1] == '5') {
179                 // 8 bit! ANSI specifies a predefined set of 256 colors here.
180                 if ((*escape_sequence)[2] != ';')
181                         return 0; /* not understood */
182                 num = strtol((*escape_sequence) + 3, escape_sequence, 10);
183                 if (num >= 0 && num <= 7) {
184                         // 0-7 are standard colors  same as SGR 30-37
185                         return num + 1;
186                 }
187                 if (num >= 8 && num <= 15) {
188                         // 8-15 are standard colors  same as SGR 90-97
189                          return more_colors ? num + 1 : num - 8 + 1;
190                 }
191
192                 // Remainder aren't yet implemented
193                 // 16-231:  6 × 6 × 6 cube (216 colors): 16 + 36 × r + 6 × g + b (0 ≤ r, g, b ≤ 5)
194                 // 232-255:  grayscale from black to white in 24 steps
195         }
196
197         return 0; /* not understood */
198 }
199
200
201 static int set_ansi_attribute(const int attrib, char** escape_sequence)
202 {
203         switch (attrib) {
204         case -1:        /* restore last settings */
205                 break;
206         case 0:         /* restore default settings */
207                 reset_ansi();
208                 break;
209         case 1:         /* set bold / increased intensity */
210                 attributes |= A_BOLD;
211                 break;
212         case 2:         /* set decreased intensity (if supported) */
213                 attributes |= A_DIM;
214                 break;
215 #ifdef A_ITALIC
216         case 3:         /* set italic (if supported) */
217                 attributes |= A_ITALIC;
218                 break;
219 #endif
220         case 4:         /* set underline */
221                 attributes |= A_UNDERLINE;
222                 break;
223         case 5:         /* set blinking */
224                 attributes |= A_BLINK;
225                 break;
226         case 7:         /* set inversed */
227                 attributes |= A_REVERSE;
228                 break;
229         case 21:        /* unset bold / increased intensity */
230                 attributes &= ~A_BOLD;
231                 break;
232         case 22:        /* unset bold / any intensity modifier */
233                 attributes &= ~(A_BOLD | A_DIM);
234                 break;
235 #ifdef A_ITALIC
236         case 23:        /* unset italic */
237                 attributes &= ~A_ITALIC;
238                 break;
239 #endif
240         case 24:        /* unset underline */
241                 attributes &= ~A_UNDERLINE;
242                 break;
243         case 25:        /* unset blinking */
244                 attributes &= ~A_BLINK;
245                 break;
246         case 27:        /* unset inversed */
247                 attributes &= ~A_REVERSE;
248                 break;
249     case 38:
250         fg_col = process_ansi_color_escape_sequence(escape_sequence);
251         if (fg_col == 0) {
252             return 0; /* not understood */
253         }
254         break;
255     case 39:
256         fg_col = 0;
257         break;
258     case 48:
259         bg_col = process_ansi_color_escape_sequence(escape_sequence);
260         if (bg_col == 0) {
261             return 0; /* not understood */
262         }
263         break;
264     case 49:
265         bg_col = 0;
266         break;
267         default:
268                 if (attrib >= 30 && attrib <= 37) {     /* set foreground color */
269                         fg_col = attrib - 30 + 1;
270                 } else if (attrib >= 40 && attrib <= 47) { /* set background color */
271                         bg_col = attrib - 40 + 1;
272                 } else if (attrib >= 90 && attrib <= 97) { /* set bright fg color */
273                         fg_col = more_colors ? attrib - 90 + 9 : attrib - 90 + 1;
274                 } else if (attrib >= 100 && attrib <= 107) { /* set bright bg color */
275                         bg_col = more_colors ? attrib - 100 + 9 : attrib - 100 + 1;
276                 } else {
277                         return 0; /* Not understood */
278                 }
279         }
280         attr_set(attributes, bg_col * nr_of_colors + fg_col + 1, NULL);
281         return 1;
282 }
283
284 static void process_ansi(FILE * fp)
285 {
286         int i, c;
287         int ansi_attribute;
288         char buf[MAX_ANSIBUF];
289         char *numstart, *endptr;
290
291         c = getc(fp);
292
293         if (c == '(') {
294                 c = getc(fp);
295                 c = getc(fp);
296         }
297         if (c != '[') {
298                 ungetc(c, fp);
299                 return;
300         }
301         for (i = 0; i < MAX_ANSIBUF; i++) {
302                 c = getc(fp);
303                 /* COLOUR SEQUENCE ENDS in 'm' */
304                 if (c == 'm') {
305                         buf[i] = '\0';
306                         break;
307                 }
308                 if ((c < '0' || c > '9') && c != ';') {
309                         return;
310                 }
311                 buf[i] = (char)c;
312         }
313         /*
314          * buf now contains a semicolon-separated list of decimal integers,
315          * each indicating an attribute to apply.
316          * For example, buf might contain "0;1;31", derived from the color
317          * escape sequence "<ESC>[0;1;31m". There can be 1 or more
318          * attributes to apply, but typically there are between 1 and 3.
319          */
320
321         /* Special case of <ESC>[m */
322         if (buf[0] == '\0')
323                 set_ansi_attribute(0, NULL);
324
325         for (endptr = numstart = buf; *endptr != '\0'; numstart = endptr + 1) {
326                 ansi_attribute = strtol(numstart, &endptr, 10);
327                 if (!set_ansi_attribute(ansi_attribute, &endptr))
328                         break;
329         }
330 }
331
332 static void __attribute__ ((__noreturn__)) do_exit(int status)
333 {
334         if (curses_started)
335                 endwin();
336         exit(status);
337 }
338
339 /* signal handler */
340 static void die(int notused __attribute__ ((__unused__)))
341 {
342         do_exit(EXIT_SUCCESS);
343 }
344
345 static void winch_handler(int notused __attribute__ ((__unused__)))
346 {
347         screen_size_changed = 1;
348 }
349
350 static char env_col_buf[24];
351 static char env_row_buf[24];
352 static int incoming_cols;
353 static int incoming_rows;
354
355 static void get_terminal_size(void)
356 {
357         struct winsize w;
358         if (!incoming_cols) {
359                 /* have we checked COLUMNS? */
360                 const char *s = getenv("COLUMNS");
361                 incoming_cols = -1;
362                 if (s && *s) {
363                         long t;
364                         char *endptr;
365                         t = strtol(s, &endptr, 0);
366                         if (!*endptr && 0 < t)
367                                 incoming_cols = t;
368                         width = incoming_cols;
369                         snprintf(env_col_buf, sizeof env_col_buf, "COLUMNS=%ld",
370                                  width);
371                         putenv(env_col_buf);
372                 }
373         }
374         if (!incoming_rows) {
375                 /* have we checked LINES? */
376                 const char *s = getenv("LINES");
377                 incoming_rows = -1;
378                 if (s && *s) {
379                         long t;
380                         char *endptr;
381                         t = strtol(s, &endptr, 0);
382                         if (!*endptr && 0 < t)
383                                 incoming_rows = t;
384                         height = incoming_rows;
385                         snprintf(env_row_buf, sizeof env_row_buf, "LINES=%ld",
386                                  height);
387                         putenv(env_row_buf);
388                 }
389         }
390         if (ioctl(STDERR_FILENO, TIOCGWINSZ, &w) == 0) {
391                 if (incoming_cols < 0 || incoming_rows < 0) {
392                         if (incoming_rows < 0 && w.ws_row > 0) {
393                                 height = w.ws_row;
394                                 snprintf(env_row_buf, sizeof env_row_buf,
395                                          "LINES=%ld", height);
396                                 putenv(env_row_buf);
397                         }
398                         if (incoming_cols < 0 && w.ws_col > 0) {
399                                 width = w.ws_col;
400                                 snprintf(env_col_buf, sizeof env_col_buf,
401                                          "COLUMNS=%ld", width);
402                                 putenv(env_col_buf);
403                         }
404                 }
405         }
406 }
407
408 /* get current time in usec */
409 typedef unsigned long long watch_usec_t;
410 #define USECS_PER_SEC (1000000ull)
411 static watch_usec_t get_time_usec()
412 {
413         struct timeval now;
414         gettimeofday(&now, NULL);
415         return USECS_PER_SEC * now.tv_sec + now.tv_usec;
416 }
417
418 #ifdef WITH_WATCH8BIT
419 /* read a wide character from a popen'd stream */
420 #define MAX_ENC_BYTES 16
421 wint_t my_getwc(FILE * s);
422 wint_t my_getwc(FILE * s)
423 {
424         /* assuming no encoding ever consumes more than 16 bytes */
425         char i[MAX_ENC_BYTES];
426         int byte = 0;
427         int convert;
428         int x;
429         wchar_t rval;
430         while (1) {
431                 i[byte] = getc(s);
432                 if (i[byte] == EOF) {
433                         return WEOF;
434                 }
435                 byte++;
436                 errno = 0;
437                 mbtowc(NULL, NULL, 0);
438                 convert = mbtowc(&rval, i, byte);
439                 x = errno;
440                 if (convert > 0) {
441                         /* legal conversion */
442                         return rval;
443                 }
444                 if (byte == MAX_ENC_BYTES) {
445                         while (byte > 1) {
446                                 /* at least *try* to fix up */
447                                 ungetc(i[--byte], s);
448                         }
449                         errno = -EILSEQ;
450                         return WEOF;
451                 }
452         }
453 }
454 #endif  /* WITH_WATCH8BIT */
455
456 #ifdef WITH_WATCH8BIT
457 static void output_header(wchar_t *restrict wcommand, int wcommand_characters, double interval)
458 #else
459 static void output_header(char *restrict command, double interval)
460 #endif  /* WITH_WATCH8BIT */
461 {
462         time_t t = time(NULL);
463         char *ts = ctime(&t);
464         char *header;
465         char *right_header;
466         int max_host_name_len = (int) sysconf(_SC_HOST_NAME_MAX);
467         char hostname[max_host_name_len + 1];
468         int command_columns = 0;        /* not including final \0 */
469
470         gethostname(hostname, sizeof(hostname));
471
472         /*
473          * left justify interval and command, right justify hostname and time,
474          * clipping all to fit window width
475          */
476         int hlen = asprintf(&header, _("Every %.1fs: "), interval);
477         int rhlen = asprintf(&right_header, _("%s: %s"), hostname, ts);
478
479         /*
480          * the rules:
481          *   width < rhlen : print nothing
482          *   width < rhlen + hlen + 1: print hostname, ts
483          *   width = rhlen + hlen + 1: print header, hostname, ts
484          *   width < rhlen + hlen + 4: print header, ..., hostname, ts
485          *   width < rhlen + hlen + wcommand_columns: print header,
486          *                           truncated wcommand, ..., hostname, ts
487          *   width > "": print header, wcomand, hostname, ts
488          * this is slightly different from how it used to be
489          */
490         if (width < rhlen) {
491                 free(header);
492                 free(right_header);
493                 return;
494         }
495         if (rhlen + hlen + 1 <= width) {
496                 mvaddstr(0, 0, header);
497                 if (rhlen + hlen + 2 <= width) {
498                         if (width < rhlen + hlen + 4) {
499                                 mvaddstr(0, width - rhlen - 4, "... ");
500                         } else {
501 #ifdef WITH_WATCH8BIT
502                                 command_columns = wcswidth(wcommand, -1);
503                                 if (width < rhlen + hlen + command_columns) {
504                                         /* print truncated */
505                                         int available = width - rhlen - hlen;
506                                         int in_use = command_columns;
507                                         int wcomm_len = wcommand_characters;
508                                         while (available - 4 < in_use) {
509                                                 wcomm_len--;
510                                                 in_use = wcswidth(wcommand, wcomm_len);
511                                         }
512                                         mvaddnwstr(0, hlen, wcommand, wcomm_len);
513                                         mvaddstr(0, width - rhlen - 4, "... ");
514                                 } else {
515                                         mvaddwstr(0, hlen, wcommand);
516                                 }
517 #else
518                                 command_columns = strlen(command);
519                                 if (width < rhlen + hlen + command_columns) {
520                                         /* print truncated */
521                                         mvaddnstr(0, hlen, command, width - rhlen - hlen - 4);
522                                         mvaddstr(0, width - rhlen - 4, "... ");
523                                 } else {
524                                         mvaddnstr(0, hlen, command, width - rhlen - hlen);
525                                 }
526 #endif  /* WITH_WATCH8BIT */
527                         }
528                 }
529         }
530         mvaddstr(0, width - rhlen + 1, right_header);
531         free(header);
532         free(right_header);
533         return;
534 }
535
536 static void find_eol(FILE *p)
537 {
538     int c;
539 #ifdef WITH_WATCH8BIT
540     do {
541         c = my_getwc(p);
542     } while (c != WEOF
543             && c!= L'\n');
544 #else
545     do {
546         c = getc(p);
547     } while (c != EOF
548             && c != '\n');
549 #endif /* WITH_WATCH8BIT */
550 }
551
552 static int run_command(char *restrict command, char **restrict command_argv)
553 {
554         FILE *p;
555         int x, y;
556         int oldeolseen = 1;
557         int pipefd[2];
558         pid_t child;
559         int exit_early = 0;
560         int status;
561
562         /* allocate pipes */
563         if (pipe(pipefd) < 0)
564                 xerr(7, _("unable to create IPC pipes"));
565
566         /* flush stdout and stderr, since we're about to do fd stuff */
567         fflush(stdout);
568         fflush(stderr);
569
570         /* fork to prepare to run command */
571         child = fork();
572
573         if (child < 0) {                /* fork error */
574                 xerr(2, _("unable to fork process"));
575         } else if (child == 0) {        /* in child */
576                 close(pipefd[0]);               /* child doesn't need read side of pipe */
577                 close(1);                       /* prepare to replace stdout with pipe */
578                 if (dup2(pipefd[1], 1) < 0) {   /* replace stdout with write side of pipe */
579                         xerr(3, _("dup2 failed"));
580                 }
581                 close(pipefd[1]);               /* once duped, the write fd isn't needed */
582                 dup2(1, 2);                     /* stderr should default to stdout */
583
584                 if (flags & WATCH_EXEC) {       /* pass command to exec instead of system */
585                         if (execvp(command_argv[0], command_argv) == -1) {
586                                 xerr(4, _("unable to execute '%s'"),
587                                      command_argv[0]);
588                         }
589                 } else {
590                         status = system(command);       /* watch manpage promises sh quoting */
591                         /* propagate command exit status as child exit status */
592                         if (!WIFEXITED(status)) {       /* child exits nonzero if command does */
593                                 exit(EXIT_FAILURE);
594                         } else {
595                                 exit(WEXITSTATUS(status));
596                         }
597                 }
598         }
599
600         /* otherwise, we're in parent */
601         close(pipefd[1]);       /* close write side of pipe */
602         if ((p = fdopen(pipefd[0], "r")) == NULL)
603                 xerr(5, _("fdopen"));
604
605         reset_ansi();
606         for (y = show_title; y < height; y++) {
607                 int eolseen = 0, tabpending = 0, tabwaspending = 0;
608                 if (flags & WATCH_COLOR)
609                         set_ansi_attribute(-1, NULL);
610 #ifdef WITH_WATCH8BIT
611                 wint_t carry = WEOF;
612 #endif
613                 for (x = 0; x < width; x++) {
614 #ifdef WITH_WATCH8BIT
615                         wint_t c = ' ';
616 #else
617                         int c = ' ';
618 #endif
619                         int attr = 0;
620
621                         if (tabwaspending && (flags & WATCH_COLOR))
622                                 set_ansi_attribute(-1, NULL);
623                         tabwaspending = 0;
624
625                         if (!eolseen) {
626                                 /* if there is a tab pending, just
627                                  * spit spaces until the next stop
628                                  * instead of reading characters */
629                                 if (!tabpending)
630 #ifdef WITH_WATCH8BIT
631                                         do {
632                                                 if (carry == WEOF) {
633                                                         c = my_getwc(p);
634                                                 } else {
635                                                         c = carry;
636                                                         carry = WEOF;
637                                                 }
638                                         } while (c != WEOF && !iswprint(c)
639                                                  && c < 128
640                                                  && wcwidth(c) == 0
641                                                  && c != L'\n'
642                                                  && c != L'\t'
643                                                  && (c != L'\033'
644                                                      || !(flags & WATCH_COLOR)));
645 #else
646                                         do
647                                                 c = getc(p);
648                                         while (c != EOF && !isprint(c)
649                                                && c != '\n'
650                                                && c != '\t'
651                                                && (c != L'\033'
652                                                    || !(flags & WATCH_COLOR)));
653 #endif
654                                 if (c == L'\033' && (flags & WATCH_COLOR)) {
655                                         x--;
656                                         process_ansi(p);
657                                         continue;
658                                 }
659                                 if (c == L'\n')
660                                         if (!oldeolseen && x == 0) {
661                                                 x = -1;
662                                                 continue;
663                                         } else
664                                                 eolseen = 1;
665                                 else if (c == L'\t')
666                                         tabpending = 1;
667 #ifdef WITH_WATCH8BIT
668                                 if (x == width - 1 && wcwidth(c) == 2) {
669                                         y++;
670                                         x = -1;         /* process this double-width */
671                                         carry = c;      /* character on the next line */
672                                         continue;       /* because it won't fit here */
673                                 }
674                                 if (c == WEOF || c == L'\n' || c == L'\t') {
675                                         c = L' ';
676                                         if (flags & WATCH_COLOR)
677                                                 attrset(A_NORMAL);
678                                 }
679 #else
680                                 if (c == EOF || c == '\n' || c == '\t') {
681                                         c = ' ';
682                                         if (flags & WATCH_COLOR)
683                                                 attrset(A_NORMAL);
684                                 }
685 #endif
686                                 if (tabpending && (((x + 1) % 8) == 0)) {
687                                         tabpending = 0;
688                                         tabwaspending = 1;
689                                 }
690                         }
691                         move(y, x);
692
693                         if (!first_screen && !exit_early && (flags & WATCH_CHGEXIT)) {
694 #ifdef WITH_WATCH8BIT
695                                 cchar_t oldc;
696                                 in_wch(&oldc);
697                                 exit_early = (wchar_t) c != oldc.chars[0];
698 #else
699                                 chtype oldch = inch();
700                                 unsigned char oldc = oldch & A_CHARTEXT;
701                                 exit_early = (unsigned char)c != oldc;
702 #endif
703                         }
704                         if (flags & WATCH_DIFF) {
705 #ifdef WITH_WATCH8BIT
706                                 cchar_t oldc;
707                                 in_wch(&oldc);
708                                 attr = !first_screen
709                                     && ((wchar_t) c != oldc.chars[0]
710                                         ||
711                                         ((flags & WATCH_CUMUL)
712                                          && (oldc.attr & A_ATTRIBUTES)));
713 #else
714                                 chtype oldch = inch();
715                                 unsigned char oldc = oldch & A_CHARTEXT;
716                                 attr = !first_screen
717                                     && ((unsigned char)c != oldc
718                                         ||
719                                         ((flags & WATCH_CUMUL)
720                                          && (oldch & A_ATTRIBUTES)));
721 #endif
722                         }
723                         if (attr)
724                                 standout();
725 #ifdef WITH_WATCH8BIT
726                         addnwstr((wchar_t *) & c, 1);
727 #else
728                         addch(c);
729 #endif
730                         if (attr)
731                                 standend();
732 #ifdef WITH_WATCH8BIT
733                         if (wcwidth(c) == 0) {
734                                 x--;
735                         }
736                         if (wcwidth(c) == 2) {
737                                 x++;
738                         }
739 #endif
740                 }
741                 oldeolseen = eolseen;
742                 if (!line_wrap) {
743                     reset_ansi();
744                     if (flags & WATCH_COLOR)
745                         attrset(A_NORMAL);
746                     find_eol(p);
747                 }
748         }
749
750         fclose(p);
751
752
753         /* harvest child process and get status, propagated from command */
754         if (waitpid(child, &status, 0) < 0)
755                 xerr(8, _("waitpid"));
756
757         /* if child process exited in error, beep if option_beep is set */
758         if ((!WIFEXITED(status) || WEXITSTATUS(status))) {
759                 if (flags & WATCH_BEEP)
760                         beep();
761                 if (flags & WATCH_ERREXIT) {
762                         mvaddstr(height - 1, 0,
763                                  _("command exit with a non-zero status, press a key to exit"));
764                         refresh();
765                         fgetc(stdin);
766                         endwin();
767                         exit(8);
768                 }
769         }
770         first_screen = 0;
771         refresh();
772         return exit_early;
773 }
774
775 int main(int argc, char *argv[])
776 {
777         int optc;
778         double interval = 2;
779         char *interval_string;
780         char *command;
781         char **command_argv;
782         int command_length = 0; /* not including final \0 */
783         watch_usec_t next_loop; /* next loop time in us, used for precise time
784                                  * keeping only */
785 #ifdef WITH_WATCH8BIT
786         wchar_t *wcommand = NULL;
787         int wcommand_characters = 0;    /* not including final \0 */
788 #endif  /* WITH_WATCH8BIT */
789
790         static struct option longopts[] = {
791                 {"color", no_argument, 0, 'c'},
792                 {"differences", optional_argument, 0, 'd'},
793                 {"help", no_argument, 0, 'h'},
794                 {"interval", required_argument, 0, 'n'},
795                 {"beep", no_argument, 0, 'b'},
796                 {"errexit", no_argument, 0, 'e'},
797                 {"chgexit", no_argument, 0, 'g'},
798                 {"exec", no_argument, 0, 'x'},
799                 {"precise", no_argument, 0, 'p'},
800                 {"no-title", no_argument, 0, 't'},
801                 {"no-wrap", no_argument, 0, 'w'},
802                 {"version", no_argument, 0, 'v'},
803                 {0, 0, 0, 0}
804         };
805
806 #ifdef HAVE_PROGRAM_INVOCATION_NAME
807         program_invocation_name = program_invocation_short_name;
808 #endif
809         setlocale(LC_ALL, "");
810         bindtextdomain(PACKAGE, LOCALEDIR);
811         textdomain(PACKAGE);
812         atexit(close_stdout);
813
814         interval_string = getenv("WATCH_INTERVAL");
815         if(interval_string != NULL)
816                 interval = strtod_nol_or_err(interval_string, _("Could not parse interval from WATCH_INTERVAL"));
817
818         while ((optc =
819                 getopt_long(argc, argv, "+bced::ghn:pvtwx", longopts, (int *)0))
820                != EOF) {
821                 switch (optc) {
822                 case 'b':
823                         flags |= WATCH_BEEP;
824                         break;
825                 case 'c':
826                         flags |= WATCH_COLOR;
827                         break;
828                 case 'd':
829                         flags |= WATCH_DIFF;
830                         if (optarg)
831                                 flags |= WATCH_CUMUL;
832                         break;
833                 case 'e':
834                         flags |= WATCH_ERREXIT;
835                         break;
836                 case 'g':
837                         flags |= WATCH_CHGEXIT;
838                         break;
839                 case 't':
840                         show_title = 0;
841                         break;
842                 case 'w':
843                         line_wrap = 0;
844                         break;
845                 case 'x':
846                         flags |= WATCH_EXEC;
847                         break;
848                 case 'n':
849                         interval = strtod_nol_or_err(optarg, _("failed to parse argument"));
850                         break;
851                 case 'p':
852                         precise_timekeeping = 1;
853                         break;
854                 case 'h':
855                         usage(stdout);
856                         break;
857                 case 'v':
858                         printf(PROCPS_NG_VERSION);
859                         return EXIT_SUCCESS;
860                 default:
861                         usage(stderr);
862                         break;
863                 }
864         }
865
866         if (interval < 0.1)
867                 interval = 0.1;
868         if (interval > UINT_MAX)
869                 interval = UINT_MAX;
870
871         if (optind >= argc)
872                 usage(stderr);
873
874         /* save for later */
875         command_argv = &(argv[optind]);
876
877         command = xstrdup(argv[optind++]);
878         command_length = strlen(command);
879         for (; optind < argc; optind++) {
880                 char *endp;
881                 int s = strlen(argv[optind]);
882                 /* space and \0 */
883                 command = xrealloc(command, command_length + s + 2);
884                 endp = command + command_length;
885                 *endp = ' ';
886                 memcpy(endp + 1, argv[optind], s);
887                 /* space then string length */
888                 command_length += 1 + s;
889                 command[command_length] = '\0';
890         }
891
892 #ifdef WITH_WATCH8BIT
893         /* convert to wide for printing purposes */
894         /*mbstowcs(NULL, NULL, 0); */
895         wcommand_characters = mbstowcs(NULL, command, 0);
896         if (wcommand_characters < 0) {
897                 fprintf(stderr, _("unicode handling error\n"));
898                 exit(EXIT_FAILURE);
899         }
900         wcommand =
901             (wchar_t *) malloc((wcommand_characters + 1) * sizeof(wcommand));
902         if (wcommand == NULL) {
903                 fprintf(stderr, _("unicode handling error (malloc)\n"));
904                 exit(EXIT_FAILURE);
905         }
906         mbstowcs(wcommand, command, wcommand_characters + 1);
907 #endif  /* WITH_WATCH8BIT */
908
909         get_terminal_size();
910
911         /* Catch keyboard interrupts so we can put tty back in a sane
912          * state.  */
913         signal(SIGINT, die);
914         signal(SIGTERM, die);
915         signal(SIGHUP, die);
916         signal(SIGWINCH, winch_handler);
917
918         /* Set up tty for curses use.  */
919         curses_started = 1;
920         initscr();
921         if (flags & WATCH_COLOR) {
922                 if (has_colors()) {
923                         start_color();
924                         use_default_colors();
925                         init_ansi_colors();
926                 } else {
927                         flags &= ~WATCH_COLOR;
928                 }
929         }
930         nonl();
931         noecho();
932         cbreak();
933
934         if (precise_timekeeping)
935                 next_loop = get_time_usec();
936
937         while (1) {
938                 if (screen_size_changed) {
939                         get_terminal_size();
940                         resizeterm(height, width);
941                         clear();
942                         /* redrawwin(stdscr); */
943                         screen_size_changed = 0;
944                         first_screen = 1;
945                 }
946
947                 if (show_title)
948 #ifdef WITH_WATCH8BIT
949                         output_header(wcommand, wcommand_characters, interval);
950 #else
951                         output_header(command, interval);
952 #endif  /* WITH_WATCH8BIT */
953
954                 if (run_command(command, command_argv))
955                         break;
956
957
958                 if (precise_timekeeping) {
959                         watch_usec_t cur_time = get_time_usec();
960                         next_loop += USECS_PER_SEC * interval;
961                         if (cur_time < next_loop)
962                                 usleep(next_loop - cur_time);
963                 } else
964                         if (interval < UINT_MAX / USECS_PER_SEC)
965                                 usleep(interval * USECS_PER_SEC);
966                         else
967                                 sleep(interval);
968         }
969
970         endwin();
971         return EXIT_SUCCESS;
972 }