/** * tcsgrep.c - match input against control sequence patterns * * Copyright (C) 2020,2021 CismonX * * 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 . */ #ifdef HAVE_CONFIG_H # include "config.h" #endif // HAVE_CONFIG_H #include #include #include #include #include #include #include #include #include #include #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; }