/* * Copyright (c) 2023–2024 DTB * SPDX-License-Identifier: AGPL-3.0-or-later * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU Affero General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) any * later version. * * This program 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 Affero General Public License for more * details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see https://www.gnu.org/licenses/. */ #include /* sigaction(2), signal(2), struct sigaction, SIGINT */ #include /* fprintf(3), fgetc(3), perror(3), fputc(3), stderr, stdin, * stdout, EOF, NULL */ #include /* exit(3), EXIT_FAILURE */ #include /* EX_IOERR, EX_OK, EX_USAGE */ #include /* tcgetattr(3), tcsetattr(3), struct termios, ECHO */ #include /* getopt(3), isatty(3), pledge(2), unveil(2), * STDIN_FILENO */ char *program_name = "peek"; /* 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. */ static void restore_echo(int sig) { static struct termios t; /* Failure isn't reported because this is the termination routine anyway; * errors will be obvious. */ if (tcgetattr(STDIN_FILENO, &t) == 0) { t.c_lflag |= ECHO; (void)tcsetattr(STDIN_FILENO, TCSAFLUSH, &t); } if (sig != 0) { exit(EXIT_FAILURE); } /* Terminated by signal. */ return; } static int ioerr(char *argv0) { perror(argv0); restore_echo(0); return EX_IOERR; } static int usage(char *argv0) { (void)fprintf(stderr, "Usage: %s [-i]\n", argv0); return EX_USAGE; } int main(int argc, char *argv[]){ #ifdef __OpenBSD__ if (pledge("stdio tty unveil", "") != 0 || unveil(NULL, NULL) != 0) { /* This isn't fatal; these return values could be cast to void just as * easily. */ (void)perror(argv[0] == NULL ? argv[0] : program_name); } #endif if (argc > 0) { /* option parsing */ char allow_nonterminals; int c; program_name = argv[0]; allow_nonterminals = 0; while ((c = getopt(argc, argv, "i")) != -1) { switch (c) { case 'i': allow_nonterminals = 1; break; default: return usage(argv[0]); } } if (argc > optind) { return usage(argv[0]); } if (!allow_nonterminals && isatty(STDIN_FILENO) != 1) { (void)fprintf( stderr, "%s: Must be run in a terminal (option -i skips this check)\n", argv[0] ); return EX_USAGE; } } { /* Install signal handler */ /* There isn't a difference in functionality between the signal(2) and * sigaction(2) methods. sigaction(2) is vastly preferred for * portability but some configurations can only use signal(2). */ /* Errors aren't terminating because the worst that happens is some * terminal phooeyness if things go awry. */ #if defined _POSIX_C_SOURCE struct sigaction act = { 0 }; act.sa_handler = restore_echo; if (sigaction(SIGINT, &act, NULL) != 0) { perror(program_name); } #else if (signal(SIGINT, restore_echo) == SIG_ERR) { perror(program_name); } #endif } /* Banish terminal echo; this terminates when it fails, because it's the * whole point of the program. */ { struct termios t; if (tcgetattr(STDIN_FILENO, &t) != 0) { return ioerr(program_name); } t.c_lflag ^= ECHO; if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &t) != 0) { return ioerr(program_name); } } { /* Input loop */ int c; while ((c = fgetc(stdin)) != EOF) { if (fputc(c, stdout) == EOF) { return ioerr(program_name); } } } restore_echo(0); return EX_OK; }