From db552d38bc2db251075a3421dd1852a5f580d3fb Mon Sep 17 00:00:00 2001 From: "Mordechai T. Abzug" Date: Tue, 24 Nov 2009 11:00:43 +1100 Subject: [PATCH] watch: add -exec and -beep flags and has better quoting Additionally add -errexit flag (#183346). A patch from Debian. Bug-Debian: http://bugs.debian.org/410967 Bug-Debian: http://bugs.debian.org/183346 Reviewed-by: Craig Small Backported-by: Sami Kerola --- watch.1 | 33 ++++++++++++++++---- watch.c | 93 +++++++++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 116 insertions(+), 10 deletions(-) diff --git a/watch.1 b/watch.1 index 16167942..802ab841 100644 --- a/watch.1 +++ b/watch.1 @@ -4,10 +4,13 @@ watch \- execute a program periodically, showing output fullscreen .SH SYNOPSIS .na .B watch -.RB [ \-dhvt ] +.RB [ \-bdehvtx ] .RB [ \-n .IR seconds ] +.RB [ \-\-beep ] .RB [ \-\-differences[=\fIcumulative\fP]] +.RB [ \-\-errexit ] +.RB [ \-\-exec ] .RB [ \-\-help ] .RB [ \-\-interval=\fIseconds\fP] .RB [ \-\-no\-title ] @@ -17,7 +20,8 @@ watch \- execute a program periodically, showing output fullscreen .B watch runs .I command -repeatedly, displaying its output (the first screenfull). This allows you to +repeatedly, displaying its output and errors (the first screenfull). This +allows you to watch the program output change over time. By default, the program is run every 2 seconds; use .B \-n @@ -37,15 +41,33 @@ positions that have ever changed. The or .B \-\-no\-title option turns off the header showing the interval, command, and current -time at the top of the display, as well as the following blank line. +time at the top of the display, as well as the following blank line. The +.I \-b +or +.I \-\-beep +option causes the command to beep if it has a non-zero exit. .PP .B watch -will run until interrupted. +will normally run until interrupted. If you want +.B watch +to exit on an error from the program running use the +.I \-e +or +.I \-\-errexit +options, which will cause +.B watch +to exit if the return value from the program is non-zero. + .SH NOTE Note that .I command is given to "sh \-c" which means that you may need to use extra quoting to get the desired effect. +You can disable this with the +.I -x +or +.I --exec +option, which passes the command to exec(2) instead. .PP Note that POSIX option processing is used (i.e., option processing stops at the first non\-option argument). This means that flags after @@ -93,4 +115,5 @@ The original .B watch was written by Tony Rems in 1991, with mods and corrections by Francois Pinard. It was reworked and new features added by -Mike Coleman in 1999. +Mike Coleman in 1999. The beep, exec, and error handling +features were added by Morty Abzug in 2008. diff --git a/watch.c b/watch.c index af7d2620..25f8964f 100644 --- a/watch.c +++ b/watch.c @@ -8,6 +8,7 @@ * Mike Coleman . * * Changes by Albert Cahalan, 2002-2003. + * stderr handling, exec, and beep option added by Morty Abzug, 2008 */ #include @@ -34,13 +35,16 @@ static struct option longopts[] = { {"differences", optional_argument, 0, 'd'}, {"help", no_argument, 0, 'h'}, {"interval", required_argument, 0, 'n'}, + {"beep", no_argument, 0, 'b'}, + {"errexit", no_argument, 0, 'e'}, + {"exec", no_argument, 0, 'x'}, {"no-title", no_argument, 0, 't'}, {"version", no_argument, 0, 'v'}, {0, 0, 0, 0} }; static char usage[] = - "Usage: %s [-dhntv] [--differences[=cumulative]] [--help] [--interval=] [--no-title] [--version] \n"; + "Usage: %s [-bdhntvx] [--beep] [--differences[=cumulative]] [--exec] [--help] [--interval=] [--no-title] [--version] \n"; static char *progname; @@ -139,28 +143,44 @@ main(int argc, char *argv[]) int optc; int option_differences = 0, option_differences_cumulative = 0, + option_exec = 0, + option_beep = 0, + option_errexit = 0, option_help = 0, option_version = 0; double interval = 2; char *command; + char **command_argv; int command_length = 0; /* not including final \0 */ + int pipefd[2]; + int status; + pid_t child; setlocale(LC_ALL, ""); progname = argv[0]; - while ((optc = getopt_long(argc, argv, "+d::hn:vt", longopts, (int *) 0)) + while ((optc = getopt_long(argc, argv, "+bed::hn:vtx", longopts, (int *) 0)) != EOF) { switch (optc) { + case 'b': + option_beep = 1; + break; case 'd': option_differences = 1; if (optarg) option_differences_cumulative = 1; break; + case 'e': + option_errexit = 1; + break; case 'h': option_help = 1; break; case 't': show_title = 0; break; + case 'x': + option_exec = 1; + break; case 'n': { char *str; @@ -190,18 +210,23 @@ main(int argc, char *argv[]) if (option_help) { fprintf(stderr, usage, progname); + fputs(" -b, --beep\t\t\t\tbeep if the command has a non-zero exit\n", stderr); fputs(" -d, --differences[=cumulative]\thighlight changes between updates\n", stderr); fputs("\t\t(cumulative means highlighting is cumulative)\n", stderr); + fputs(" -e, --errexit\t\t\t\texit watch if the command has a non-zero exit\n", stderr); fputs(" -h, --help\t\t\t\tprint a summary of the options\n", stderr); fputs(" -n, --interval=\t\tseconds to wait between updates\n", stderr); fputs(" -v, --version\t\t\t\tprint the version number\n", stderr); fputs(" -t, --no-title\t\t\tturns off showing the header\n", stderr); + fputs(" -x, --exec\t\t\t\tpass command to exec instead of sh\n", stderr); exit(0); } if (optind >= argc) do_usage(); + command_argv=&(argv[optind]); /* save for later */ + command = strdup(argv[optind++]); command_length = strlen(command); for (; optind < argc; optind++) { @@ -260,11 +285,57 @@ main(int argc, char *argv[]) free(header); } - if (!(p = popen(command, "r"))) { - perror("popen"); + /* allocate pipes */ + if (pipe(pipefd)<0) { + perror("pipe"); + do_exit(7); + } + + /* 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 */ + perror("fork"); do_exit(2); + } 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 */ + perror("dup2"); + exit(3); + } + dup2(1, 2); /* stderr should default to stdout */ + + if (option_exec) { /* pass command to exec instead of system */ + if (execvp(command_argv[0], command_argv)==-1) { + perror("exec"); + exit(4); + } + } 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(1); + } else { + exit(WEXITSTATUS(status)); + } + } + } + /* otherwise, we're in parent */ + close(pipefd[1]); /* close write side of pipe */ + if ((p=fdopen(pipefd[0], "r"))==NULL) { + perror("fdopen"); + do_exit(5); + } + + for (y = show_title; y < height; y++) { int eolseen = 0, tabpending = 0; for (x = 0; x < width; x++) { @@ -312,7 +383,19 @@ main(int argc, char *argv[]) oldeolseen = eolseen; } - pclose(p); + fclose(p); + + /* harvest child process and get status, propagated from command */ + if (waitpid(child, &status, 0)<0) { + perror("waitpid"); + do_exit(8); + }; + + /* if child process exited in error, beep if option_beep is set */ + if ((!WIFEXITED(status) || WEXITSTATUS(status))) { + if (option_beep) beep(); + if (option_errexit) do_exit(8); + } first_screen = 0; refresh();