Helper library for control sequences. https://nongnu.org/ctlseqs
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

492 lines
16 KiB

/**
* tcsgrep.c - match input against control sequence patterns
*
* Copyright (C) 2020,2021 CismonX <admin@cismon.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif // HAVE_CONFIG_H
#include <ctype.h>
#include <errno.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <getopt.h>
#include <termios.h>
#include <unistd.h>
#include <ctlseqs.h>
#define NINTH_ARG_(a1, a2, a3, a4, a5, a6, a7, a8, a9, ...) a9
#define COUNT_ARGS_(...) NINTH_ARG_(__VA_ARGS__, 8, 7, 6, 5, 4, 3, 2, 1, 0)
#define CONCAT_1_(a1) a1
#define CONCAT_2_(a1, a2) a1 a2
#define CONCAT_3_(a1, a2, a3) a1 a2 a3
#define CONCAT_4_(a1, a2, a3, a4) a1 a2 a3 a4
#define CONCAT_5_(a1, a2, a3, a4, a5) a1 a2 a3 a4 a5
#define CONCAT_6_(a1, a2, a3, a4, a5, a6) a1 a2 a3 a4 a5 a6
#define CONCAT_7_(a1, a2, a3, a4, a5, a6, a7) a1 a2 a3 a4 a5 a6 a7
#define CONCAT_8_(a1, a2, a3, a4, a5, a6, a7, a8) a1 a2 a3 a4 a5 a6 a7 a8
#define CONCAT_(nargs) CONCAT_##nargs##_
#define DEFARGS_(nargs, ...) CONCAT_(nargs)(__VA_ARGS__)
#define DEFSEQ_NOARGS(name) { #name, CTLSEQS_##name(), "" }
#define DEFSEQ(name, ...) { #name, CTLSEQS_##name(__VA_ARGS__), \
DEFARGS_(COUNT_ARGS_(__VA_ARGS__), __VA_ARGS__) }
#define DEFAULT_MAX_BUFFER_LEN 4096
struct tcsgrep_sequence {
char const *name;
char const *pattern;
char const args[8];
};
struct tcsgrep_ctx {
char const *prog_name;
FILE *out_file;
FILE *err_file;
int in_fd;
int timeout;
int limit;
bool purge_long_seqs;
bool verbose;
bool not_tty;
};
static inline void
print_error(
struct tcsgrep_ctx const *ctx,
char const *msg
) {
fprintf(ctx->err_file, "%s: [error] %s.\n", ctx->prog_name, msg);
}
static inline bool
parse_int(
char const *str,
int *dest
) {
errno = 0;
unsigned long result = strtoul(str, NULL, 10);
if (errno || result > 4096) {
return false;
}
*dest = result;
return true;
}
static inline void
print_char(
struct tcsgrep_ctx const *ctx,
int ch
) {
static char const *ascii_table[] = {
"NUL", "SOH", "STX", "ETX", "EOT", "ENQ", "ACK", "BEL",
"BS", "HT", "LF", "VT", "FF", "CR", "SO", "SI",
"DLE", "DC1", "DC2", "DC3", "DC4", "NAK", "SYN", "ETB",
"CAN", "EM", "SUB", "ESC", "FS", "GS", "RS", "US"
};
if (ch == ' ') {
fwrite(" SP", 3, 1, ctx->out_file);
} else if (isprint(ch)) {
fprintf(ctx->out_file, " %c", ch);
} else if (ch == 0x7f) {
fwrite(" DEL", 4, 1, ctx->out_file);
} else if (!iscntrl(ch)) {
fprintf(ctx->out_file, " \\x%02x", (unsigned char)ch);
} else {
fprintf(ctx->out_file, " %s", ascii_table[ch]);
}
}
static void
print_generic_seq(
struct tcsgrep_ctx const *ctx,
char const *header,
union ctlseqs_value const *result,
bool newline
) {
size_t length = result[0].len;
char const *seq = result[1].str;
fprintf(ctx->out_file, "%s %zu", header, length);
for (size_t idx = 0; idx < length; ++idx) {
print_char(ctx, (unsigned)seq[idx]);
}
if (newline) {
fwrite("\n", 1, 1, ctx->out_file);
}
}
static void
print_matching_seq(
struct tcsgrep_ctx const *ctx,
struct tcsgrep_sequence const *seq,
union ctlseqs_value const *result,
bool verbose
) {
if (verbose) {
print_generic_seq(ctx, "OK", result, false);
result += 2;
} else {
fwrite("OK", 3, 1, ctx->out_file);
}
fprintf(ctx->out_file, " %s", seq->name);
for (int idx = 0; idx < 8; ++idx) {
char placeholder = seq->args[idx];
switch (placeholder) {
case 0x0e: // CTLSEQS_PH_NUM
fprintf(ctx->out_file, " %lu", result[idx].num);
break;
case 0x10: // CTLSEQS_PH_STR
fprintf(ctx->out_file, " %.*s",
(int)result[idx].len, result[idx + 1].str);
break;
case 0x0f: // CTLSEQS_PH_NUMS
for (size_t i = 1; i <= result[idx].len; ++i) {
fprintf(ctx->out_file, " %lu", result[idx + i].num);
}
break;
}
}
fprintf(ctx->out_file, "\n");
}
int
main(
int argc,
char **argv
) {
struct tcsgrep_ctx ctx = {
.prog_name = argv[0],
.out_file = stdout,
.err_file = stderr,
.in_fd = STDIN_FILENO,
.timeout = -1,
.limit = DEFAULT_MAX_BUFFER_LEN,
};
int opt;
while (-1 != (opt = getopt(argc, argv, "t:l:pv"))) {
switch (opt) {
case 't':
if (!parse_int(optarg, &ctx.timeout)) {
print_error(&ctx, "invalid timeout option");
return 1;
}
break;
case 'l':
if (!parse_int(optarg, &ctx.limit)) {
print_error(&ctx, "invalid limit option");
return 1;
}
break;
case 'p':
ctx.purge_long_seqs = true;
break;
case 'v':
ctx.verbose = true;
break;
case '?':
default:
fprintf(ctx.out_file, "%s\n",
"Usage: tcsgrep [-t timeout] [-l limit] [-pv]");
return 1;
}
}
int flags = fcntl(ctx.in_fd, F_GETFL);
if (flags == -1) {
print_error(&ctx, "failed to get file status flags");
return 1;
}
if (-1 == fcntl(ctx.in_fd, F_SETFL, flags | O_NONBLOCK)) {
print_error(&ctx, "failed to set file status flags");
return 1;
}
struct ctlseqs_matcher *matcher = ctlseqs_matcher_init();
if (matcher == NULL) {
print_error(&ctx, "failed to initialize matcher");
return 1;
}
static struct tcsgrep_sequence seqs[] = {
DEFSEQ_NOARGS(S7C1T),
DEFSEQ_NOARGS(S8C1T),
DEFSEQ_NOARGS(DECDHL_TOP),
DEFSEQ_NOARGS(DECDHL_BOTOM),
DEFSEQ_NOARGS(DECSWL),
DEFSEQ_NOARGS(DECDWL),
DEFSEQ_NOARGS(DECALN),
DEFSEQ_NOARGS(DECBI),
DEFSEQ_NOARGS(DECSC),
DEFSEQ_NOARGS(DECRC),
DEFSEQ_NOARGS(DECFI),
DEFSEQ_NOARGS(DECKPAM),
DEFSEQ_NOARGS(DECKPNM),
DEFSEQ_NOARGS(RIS),
DEFSEQ(DECUDK, CTLSEQS_PH_NUM, CTLSEQS_PH_NUM, CTLSEQS_PH_STR),
DEFSEQ(DECRQSS, CTLSEQS_PH_STR),
DEFSEQ(DECRSPS, CTLSEQS_PH_NUM, CTLSEQS_PH_STR),
DEFSEQ(XTGETXRES, CTLSEQS_PH_STR),
DEFSEQ(XTSETTCAP, CTLSEQS_PH_STR),
DEFSEQ(XTGETTCAP, CTLSEQS_PH_STR),
DEFSEQ(ICH, CTLSEQS_PH_NUM),
DEFSEQ(SL, CTLSEQS_PH_NUM),
DEFSEQ(CUU, CTLSEQS_PH_NUM),
DEFSEQ(CUD, CTLSEQS_PH_NUM),
DEFSEQ(CUF, CTLSEQS_PH_NUM),
DEFSEQ(CUB, CTLSEQS_PH_NUM),
DEFSEQ(CNL, CTLSEQS_PH_NUM),
DEFSEQ(CPL, CTLSEQS_PH_NUM),
DEFSEQ(CHA, CTLSEQS_PH_NUM),
DEFSEQ(CUP, CTLSEQS_PH_NUM, CTLSEQS_PH_NUM),
DEFSEQ(CHT, CTLSEQS_PH_NUM),
DEFSEQ(ED, CTLSEQS_PH_NUM),
DEFSEQ(DECSED, CTLSEQS_PH_NUM),
DEFSEQ(EL, CTLSEQS_PH_NUM),
DEFSEQ(DECSEL, CTLSEQS_PH_NUM),
DEFSEQ(IL, CTLSEQS_PH_NUM),
DEFSEQ(DL, CTLSEQS_PH_NUM),
DEFSEQ(DCH, CTLSEQS_PH_NUM),
DEFSEQ(SU, CTLSEQS_PH_NUM),
DEFSEQ(XTSMGRAPHICS, CTLSEQS_PH_NUM, CTLSEQS_PH_NUM, CTLSEQS_PH_NUMS),
DEFSEQ(SD, CTLSEQS_PH_NUM),
DEFSEQ(XTHIMOUSE, CTLSEQS_PH_NUM, CTLSEQS_PH_NUM, CTLSEQS_PH_NUM,
CTLSEQS_PH_NUM, CTLSEQS_PH_NUM),
DEFSEQ(XTRMTITLE, CTLSEQS_PH_STR),
DEFSEQ(ECH, CTLSEQS_PH_NUM),
DEFSEQ(CBT, CTLSEQS_PH_NUM),
DEFSEQ(HPA, CTLSEQS_PH_NUM),
DEFSEQ(HPR, CTLSEQS_PH_NUM),
DEFSEQ(REP, CTLSEQS_PH_NUM),
DEFSEQ_NOARGS(PRIMARY_DA),
DEFSEQ_NOARGS(TERTIARY_DA),
DEFSEQ_NOARGS(SECONDARY_DA),
DEFSEQ(VPA, CTLSEQS_PH_NUM),
DEFSEQ(VPR, CTLSEQS_PH_NUM),
DEFSEQ(HVP, CTLSEQS_PH_NUM, CTLSEQS_PH_NUM),
DEFSEQ(TBC, CTLSEQS_PH_NUM),
DEFSEQ(SM, CTLSEQS_PH_NUM),
DEFSEQ(DECSET, CTLSEQS_PH_NUM),
DEFSEQ(MC, CTLSEQS_PH_NUM),
DEFSEQ(MC_DEC, CTLSEQS_PH_NUM),
DEFSEQ(RM, CTLSEQS_PH_NUM),
DEFSEQ(DECRST, CTLSEQS_PH_NUM),
DEFSEQ(SGR, CTLSEQS_PH_NUM),
DEFSEQ(XTMODKEYS, CTLSEQS_PH_NUMS),
DEFSEQ(DSR, CTLSEQS_PH_NUM),
DEFSEQ(XTMODKEYS_DISABLE, CTLSEQS_PH_NUM),
DEFSEQ(DSR_DEC, CTLSEQS_PH_NUM),
DEFSEQ(XTSMPOINTER, CTLSEQS_PH_NUM),
DEFSEQ_NOARGS(DECSTR),
DEFSEQ(DECSCL, CTLSEQS_PH_NUM, CTLSEQS_PH_NUM),
DEFSEQ(DECRQM_ANSI, CTLSEQS_PH_NUM),
DEFSEQ(DECRQM, CTLSEQS_PH_NUM),
DEFSEQ_NOARGS(XTVERSION),
DEFSEQ(DECLL, CTLSEQS_PH_NUM),
DEFSEQ(DECSCUSR, CTLSEQS_PH_NUM),
DEFSEQ(DECSCA, CTLSEQS_PH_NUM),
DEFSEQ(DECSTBM, CTLSEQS_PH_NUM, CTLSEQS_PH_NUM),
DEFSEQ(XTRESTORE, CTLSEQS_PH_NUMS),
DEFSEQ(DECCARA, CTLSEQS_PH_NUM, CTLSEQS_PH_NUM, CTLSEQS_PH_NUM,
CTLSEQS_PH_NUM, CTLSEQS_PH_NUM),
DEFSEQ_NOARGS(SCOSC),
DEFSEQ(DECSLRM, CTLSEQS_PH_NUM, CTLSEQS_PH_NUM),
DEFSEQ(XTSAVE, CTLSEQS_PH_NUMS),
DEFSEQ(XTWINOPS, CTLSEQS_PH_NUMS),
DEFSEQ(XTSMTITLE, CTLSEQS_PH_NUMS),
DEFSEQ(DECSWBV, CTLSEQS_PH_NUM),
DEFSEQ(DECRARA, CTLSEQS_PH_NUM, CTLSEQS_PH_NUM, CTLSEQS_PH_NUM,
CTLSEQS_PH_NUM, CTLSEQS_PH_NUM),
DEFSEQ_NOARGS(SCORC),
DEFSEQ(DECSMBV, CTLSEQS_PH_NUM),
DEFSEQ(DECCRA,
CTLSEQS_PH_NUM, CTLSEQS_PH_NUM, CTLSEQS_PH_NUM, CTLSEQS_PH_NUM,
CTLSEQS_PH_NUM, CTLSEQS_PH_NUM, CTLSEQS_PH_NUM, CTLSEQS_PH_NUM),
DEFSEQ(DECRQPSR, CTLSEQS_PH_NUM),
DEFSEQ(DECEFR,
CTLSEQS_PH_NUM, CTLSEQS_PH_NUM, CTLSEQS_PH_NUM, CTLSEQS_PH_NUM),
DEFSEQ(DECREQTPARM, CTLSEQS_PH_NUM),
DEFSEQ(DECEFR,
CTLSEQS_PH_NUM, CTLSEQS_PH_NUM, CTLSEQS_PH_NUM, CTLSEQS_PH_NUM),
DEFSEQ(DECREQTPARM, CTLSEQS_PH_NUM),
DEFSEQ(DECSACE, CTLSEQS_PH_NUM),
DEFSEQ(DECFRA, CTLSEQS_PH_NUM, CTLSEQS_PH_NUM, CTLSEQS_PH_NUM,
CTLSEQS_PH_NUM, CTLSEQS_PH_NUM),
DEFSEQ(XTCHECKSUM, CTLSEQS_PH_NUM),
DEFSEQ(DECRQCRA, CTLSEQS_PH_NUM, CTLSEQS_PH_NUM, CTLSEQS_PH_NUM,
CTLSEQS_PH_NUM, CTLSEQS_PH_NUM, CTLSEQS_PH_NUM),
DEFSEQ(DECELR, CTLSEQS_PH_NUM, CTLSEQS_PH_NUM),
DEFSEQ(DECERA,
CTLSEQS_PH_NUM, CTLSEQS_PH_NUM, CTLSEQS_PH_NUM, CTLSEQS_PH_NUM),
DEFSEQ(DECSLE, CTLSEQS_PH_NUMS),
DEFSEQ(XTPUSHSGR, CTLSEQS_PH_NUMS),
DEFSEQ(DECSERA,
CTLSEQS_PH_NUM, CTLSEQS_PH_NUM, CTLSEQS_PH_NUM, CTLSEQS_PH_NUM),
DEFSEQ(XTREPORTSGR,
CTLSEQS_PH_NUM, CTLSEQS_PH_NUM, CTLSEQS_PH_NUM, CTLSEQS_PH_NUM),
DEFSEQ(DECSCPP, CTLSEQS_PH_NUM),
DEFSEQ_NOARGS(DECRQLP),
DEFSEQ(DECSNLS, CTLSEQS_PH_NUM),
DEFSEQ_NOARGS(XTPOPSGR),
DEFSEQ(DECIC, CTLSEQS_PH_NUM),
DEFSEQ(DECDC, CTLSEQS_PH_NUM),
DEFSEQ(OSC_SEQ, CTLSEQS_PH_NUM, CTLSEQS_PH_STR),
DEFSEQ(RESP_DECRQSS, CTLSEQS_PH_NUM, CTLSEQS_PH_STR),
DEFSEQ(RESP_XTGETXRES, CTLSEQS_PH_NUM, CTLSEQS_PH_STR),
DEFSEQ(RESP_XTGETTCAP, CTLSEQS_PH_NUM, CTLSEQS_PH_STR),
DEFSEQ(RESP_PRIMARY_DA, CTLSEQS_PH_NUMS),
DEFSEQ(RESP_SECONDARY_DA,
CTLSEQS_PH_NUM, CTLSEQS_PH_NUM, CTLSEQS_PH_NUM),
DEFSEQ(RESP_DECXCPR, CTLSEQS_PH_NUM, CTLSEQS_PH_NUM),
DEFSEQ(RESP_DSR, CTLSEQS_PH_NUMS),
DEFSEQ(RESP_DECRQM_ANSI, CTLSEQS_PH_NUM, CTLSEQS_PH_NUM),
DEFSEQ(RESP_DECRQM, CTLSEQS_PH_NUM, CTLSEQS_PH_NUM),
DEFSEQ(RESP_XTVERSION, CTLSEQS_PH_STR),
DEFSEQ(RESP_DECCIR, CTLSEQS_PH_STR),
DEFSEQ(RESP_DECTABSR, CTLSEQS_PH_STR),
DEFSEQ(RESP_DECRQCRA, CTLSEQS_PH_NUM, CTLSEQS_PH_STR),
DEFSEQ(RESP_DECRQLP, CTLSEQS_PH_NUM, CTLSEQS_PH_NUM, CTLSEQS_PH_NUM,
CTLSEQS_PH_NUM, CTLSEQS_PH_NUM),
DEFSEQ_NOARGS(KEY_UP),
DEFSEQ_NOARGS(KEY_DOWN),
DEFSEQ_NOARGS(KEY_RIGHT),
DEFSEQ_NOARGS(KEY_LEFT),
DEFSEQ_NOARGS(KEY_HOME),
DEFSEQ_NOARGS(KEY_END),
DEFSEQ_NOARGS(KEY_INSERT),
DEFSEQ_NOARGS(KEY_DELETE),
DEFSEQ_NOARGS(KEY_PGUP),
DEFSEQ_NOARGS(KEY_PGDN),
DEFSEQ_NOARGS(KEY_F1),
DEFSEQ_NOARGS(KEY_F2),
DEFSEQ_NOARGS(KEY_F3),
DEFSEQ_NOARGS(KEY_F4),
DEFSEQ_NOARGS(KEY_F5),
DEFSEQ_NOARGS(KEY_F6),
DEFSEQ_NOARGS(KEY_F7),
DEFSEQ_NOARGS(KEY_F8),
DEFSEQ_NOARGS(KEY_F9),
DEFSEQ_NOARGS(KEY_F10),
DEFSEQ_NOARGS(KEY_F11),
DEFSEQ_NOARGS(KEY_F12),
};
size_t npatterns = sizeof(seqs) / sizeof(struct tcsgrep_sequence);
char const *patterns[npatterns];
for (size_t idx = 0; idx < npatterns; ++idx) {
patterns[idx] = seqs[idx].pattern;
}
struct ctlseqs_matcher_options matcher_options = {
.patterns = patterns,
.npatterns = npatterns,
};
if (CTLSEQS_OK != ctlseqs_matcher_config(matcher, &matcher_options)) {
print_error(&ctx, "matcher setopt failed");
return 1;
}
struct ctlseqs_reader *reader = ctlseqs_reader_init();
if (reader == NULL) {
print_error(&ctx, "failed to initialize reader");
}
static union ctlseqs_value result[4096];
struct ctlseqs_reader_options reader_options = {
.fd = ctx.in_fd,
.maxlen = ctx.limit,
.result = result,
.flags = ctx.verbose ? CTLSEQS_READER_SAVE_MATCHED_SEQS : 0,
};
if (CTLSEQS_OK != ctlseqs_reader_config(reader, &reader_options)) {
print_error(&ctx, "reader setopt failed");
return 1;
}
struct termios old_termios;
if (0 != tcgetattr(ctx.in_fd, &old_termios)) {
ctx.not_tty = true;
} else {
struct termios new_termios = old_termios;
new_termios.c_cc[VMIN] = 0;
new_termios.c_cc[VTIME] = 0;
new_termios.c_lflag &= ~(ICANON | ISIG | ECHO);
if (tcsetattr(ctx.in_fd, TCSANOW, &new_termios) != 0) {
print_error(&ctx, "failed to set terminal attributes");
return 1;
}
}
int status = 0;
while (true) {
ssize_t retval = ctlseqs_read(reader, matcher, ctx.timeout);
switch (retval) {
case CTLSEQS_ERROR:
print_error(&ctx, "unexpected error");
status = 1;
goto terminate;
case CTLSEQS_TIMEOUT:
fprintf(ctx.out_file, "TIMEOUT\n");
status = 1;
goto terminate;
case CTLSEQS_INTR:
fprintf(ctx.out_file, "INTR\n");
break;
case CTLSEQS_EOF:
fprintf(ctx.out_file, "EOF\n");
goto terminate;
case CTLSEQS_PARTIAL:
if (ctx.verbose) {
print_generic_seq(&ctx, "PARTIAL", result, true);
}
break;
case CTLSEQS_NOMATCH:
print_generic_seq(&ctx, "NOMATCH", result, true);
break;
case CTLSEQS_NOMEM:
print_generic_seq(&ctx, "NOMEM", result, true);
if (ctx.purge_long_seqs) {
ctlseqs_purge(reader, result[0].len);
break;
} else {
status = 1;
goto terminate;
}
case CTLSEQS_NOSEQ:
print_generic_seq(&ctx, "NOSEQ", result, true);
if (!ctx.not_tty && result[1].str[0] == 0x04) {
goto terminate;
}
break;
default:
print_matching_seq(&ctx, &seqs[retval], result, ctx.verbose);
break;
}
}
terminate:
ctlseqs_matcher_free(matcher);
ctlseqs_reader_free(reader);
if (!ctx.not_tty) {
tcsetattr(ctx.in_fd, TCSANOW, &old_termios);
}
return status;
}