138 lines
3.8 KiB
C
138 lines
3.8 KiB
C
/*
|
||
* Copyright (c) 2023–2024 DTB <trinity@trinity.moe>
|
||
* 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 <signal.h> /* sigaction(2), signal(2), struct sigaction, SIGINT */
|
||
#include <stdio.h> /* fprintf(3), fgetc(3), perror(3), fputc(3), stderr, stdin,
|
||
* stdout, EOF, NULL */
|
||
#include <stdlib.h> /* exit(3), EXIT_FAILURE */
|
||
#include <sysexits.h> /* EX_IOERR, EX_OK, EX_USAGE */
|
||
#include <termios.h> /* tcgetattr(3), tcsetattr(3), struct termios, ECHO */
|
||
#include <unistd.h> /* 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;
|
||
}
|