From 6b90e0e1da2d82f571fd6166b6275c8e66c4a69e Mon Sep 17 00:00:00 2001 From: DTB Date: Wed, 17 Apr 2024 14:21:11 -0600 Subject: [PATCH] peek(1): handle ^C elegantly --- peek/peek.c | 107 ++++++++++++++++++++++++++++++++++------------------ 1 file changed, 71 insertions(+), 36 deletions(-) diff --git a/peek/peek.c b/peek/peek.c index 78f4725..d6915c6 100644 --- a/peek/peek.c +++ b/peek/peek.c @@ -1,6 +1,7 @@ +#include /* sigaction(2), struct sigaction, SIGINT */ #include /* fprintf(3), getc(3), perror(3), putc(3), stderr, stdin, * stdout, EOF, NULL */ -#include /* size_t */ +#include /* exit(3), size_t, EXIT_FAILURE */ #include /* strerror(3) */ #if !defined EX_OK || !defined EX_OSERR || !defined EX_USAGE # include @@ -12,16 +13,31 @@ static int oserr(char *s){ perror(s); return EX_OSERR; } static char *program_name = "peek"; + static int usage(char *s){ fprintf(stderr, "Usage: %s (-1enot) (-p [program [arguments...]])\n", s == NULL ? program_name : s); return EX_USAGE; } +/* Restores terminal echo; otherwise when a user ^Cs the terminal would + * continue to not display typed text. If sig isn't zero, this will terminate + * the program. */ +void restore_echo(int sig){ + static struct termios t; + + tcgetattr(STDIN_FILENO, &t); + t.c_lflag |= ECHO; + tcsetattr(STDIN_FILENO, TCSAFLUSH, &t); + + if(sig != 0) + exit(EXIT_FAILURE); + else + return; +} + int main(int argc, char *argv[]){ - int c; int eof; - size_t i; char include_eof; FILE *outputs[] = { NULL /* stdout */, @@ -29,31 +45,34 @@ int main(int argc, char *argv[]){ NULL /* -p */ }; int p[2] = {0, 0}; - struct termios t; if(argc < 1) usage(argv[0]); eof = EOF; include_eof = 0; - while((c = getopt(argc, argv, "1enopt")) != -1) - switch(c){ - case '1': eof = '\n'; break; - case 'n': include_eof = 1; break; - case 'o': outputs[0] = stdout; break; - case 'e': outputs[1] = stderr; break; - case 'p': - if(pipe(p) != 0 || (outputs[2] = fdopen(p[1], "ab")) == NULL) - return oserr(argv[0]); - break; - case 't': - if(isatty(STDIN_FILENO) != 1){ - fprintf(stderr, "%s: Must be run in a terminal" - " (option -t specified)\n", argv[0]); - return EX_USAGE; + { /* options parsing */ + int c; + + while((c = getopt(argc, argv, "1enopt")) != -1) + switch(c){ + case '1': eof = '\n'; break; + case 'n': include_eof = 1; break; + case 'o': outputs[0] = stdout; break; + case 'e': outputs[1] = stderr; break; + case 'p': + if(pipe(p) != 0 || (outputs[2] = fdopen(p[1], "ab")) == NULL) + return oserr(argv[0]); + break; + case 't': + if(isatty(STDIN_FILENO) != 1){ + fprintf(stderr, "%s: Must be run in a terminal" + " (option -t specified)\n", argv[0]); + return EX_USAGE; + } + default: return usage(argv[0]); } - default: return usage(argv[0]); - } + } /* If -p is used there must be additional arguments. getopt(3) wouldn't * work for this because optarg would have to be one string to give to @@ -75,23 +94,39 @@ int main(int argc, char *argv[]){ return oserr(argv[0]); } - /* terminal echo */ - tcgetattr(STDIN_FILENO, &t); - t.c_lflag ^= ECHO; - tcsetattr(STDIN_FILENO, TCSAFLUSH, &t); + { /* install signal handler for ^C to avoid terminal phooeyness */ + struct sigaction act = { 0 }; - do{ if((c = getc(stdin)) != eof || include_eof) - for(i = 0; i < (sizeof outputs)/(sizeof *outputs); ++i) - if(outputs[i] != NULL && putc(c, outputs[i]) == EOF){ - if(outputs[i] != stdout && outputs[i] != stderr) - fclose(outputs[i]); - outputs[i] = NULL; - } - }while(c != eof); + act.sa_handler = restore_echo; + /* This error isn't terminating because the worst that happens is some + * terminal phooeyness if things go awry. */ + if(sigaction(SIGINT, &act, NULL) != 0) + perror(argv[0]); + } + + { /* banish terminal echo */ + struct termios t; - tcgetattr(STDIN_FILENO, &t); - t.c_lflag |= ECHO; - tcsetattr(STDIN_FILENO, TCSAFLUSH, &t); + tcgetattr(STDIN_FILENO, &t); + t.c_lflag ^= ECHO; + tcsetattr(STDIN_FILENO, TCSAFLUSH, &t); + } + + { /* actual input loop */ + int c; + size_t i; + + do{ if((c = getc(stdin)) != eof || include_eof) + for(i = 0; i < (sizeof outputs)/(sizeof *outputs); ++i) + if(outputs[i] != NULL && putc(c, outputs[i]) == EOF){ + if(outputs[i] != stdout && outputs[i] != stderr) + fclose(outputs[i]); + outputs[i] = NULL; + } + }while(c != eof); + } + + restore_echo(0); return EX_OK; }