1#include <err.h>2#include <errno.h>3#include <fcntl.h>4#include <libgen.h>5#include <limits.h>6#include <locale.h>7#include <signal.h>8#include <stdio.h>9#include <stdlib.h>10#include <string.h>11#include <unistd.h>1213#include <readline/history.h>14#include <readline/readline.h>1516#include <sys/stat.h>17#include <sys/types.h>1819#define GREPCMD "grep -F -f "20#define TTYDEVP "/dev/tty"21#define LEN(X) (sizeof(X) / sizeof(X[0]))2223static char *cmdbuf;24static char *histfp;2526static int fdtemp = -1;27static char fntemp[] = "/tmp/inputXXXXXX";28static int signals[] = {SIGINT, SIGTERM, SIGQUIT, SIGHUP};2930static void31usage(char *prog)32{33 char *usage = "[-1] [-w] [-c COMMAND] [-p PROMPT] "34 "[-h HISTORY] [-s HISTSIZE]";3536 fprintf(stderr, "USAGE: %s %s\n", basename(prog), usage);37 exit(EXIT_FAILURE);38}3940static void41cleanup(void)42{43 static volatile sig_atomic_t clean;4445 if (clean)46 return; /* don't cleanup twice */4748 if (histfp) {49 if (write_history(histfp))50 warn("saving history to '%s' failed", histfp);51 }5253 if (fdtemp > 0) {54 if (close(fdtemp) == -1)55 warn("close failed for '%s'", fntemp);56 if (remove(fntemp) == -1)57 warn("couldn't remove file '%s'", fntemp);58 }5960 clean = 1;61}6263static void64onexit(void)65{66 sigset_t blockset;6768 /* cleanup is called atexit(3) and from a signal handler, block69 * all signals here before invoking cleanup to ensure we are not70 * interrupted by the signal handler during cleanup. */7172 if (sigfillset(&blockset) == -1)73 err(EXIT_FAILURE, "sigfillset failed");74 if (sigprocmask(SIG_BLOCK, &blockset, NULL))75 err(EXIT_FAILURE, "sigprocmask failed");7677 cleanup();78}7980static void81sighandler(int num)82{83 (void)num;8485 cleanup();86 exit(EXIT_FAILURE);87}8889static void90sethandler(void)91{92 size_t i;93 struct sigaction act;9495 act.sa_flags = 0;96 act.sa_handler = sighandler;97 if (sigemptyset(&act.sa_mask) == -1)98 err(EXIT_FAILURE, "sigemptyset failed");99100 for (i = 0; i < LEN(signals); i++) {101 if (sigaction(signals[i], &act, NULL))102 err(EXIT_FAILURE, "sigaction failed");103 }104}105106static FILE *107fout(void)108{109 FILE *out;110111 out = stdout;112 if (isatty(fileno(out)))113 return out;114115 if (!(out = fopen(TTYDEVP, "w")))116 err(EXIT_FAILURE, "fopen failed");117 return out;118}119120static char *121safegrep(const char *pattern, size_t len)122{123 if (ftruncate(fdtemp, 0) == -1)124 err(EXIT_FAILURE, "ftruncate failed");125 if (lseek(fdtemp, SEEK_SET, 0) == -1)126 err(EXIT_FAILURE, "lseek failed");127128 if (write(fdtemp, pattern, len) == -1 ||129 write(fdtemp, "\n", 1) == -1)130 err(EXIT_FAILURE, "write failed");131132 return cmdbuf;133}134135static char *136gencomp(const char *input, int state)137{138 ssize_t n;139 size_t inlen;140 char *r, *cmd;141 static FILE *pipe;142 static char *line;143 static size_t llen;144145 inlen = strlen(input);146147 if (!state) {148 cmd = safegrep(input, inlen);149 if (!(pipe = popen(cmd, "r")))150 err(EXIT_FAILURE, "popen failed");151 }152153 while ((n = getline(&line, &llen, pipe)) > 0) {154 if (strncmp(input, line, inlen))155 continue;156 if (line[n - 1] == '\n')157 line[n - 1] = '\0';158159 if (!(r = strdup(line)))160 err(EXIT_FAILURE, "strdup failed");161 return r;162 }163 if (ferror(pipe))164 errx(EXIT_FAILURE, "ferror failed");165166 if (pclose(pipe) == -1)167 errx(EXIT_FAILURE, "pclose failed");168169 return NULL;170}171172static char **173comp(const char *text, int start, int end)174{175 (void)start;176 (void)end;177178 /* Prevent readline from performing file completions */179 rl_attempted_completion_over = 1;180181 /* Don't append any character to completions */182 rl_completion_append_character = '\0';183184#if RL_VERSION_MAJOR >= 6185 /* Don't sort completions */186 rl_sort_completion_matches = 0;187#endif188189 return rl_completion_matches(text, gencomp);190}191192static void193iloop(int single, char *prompt)194{195 size_t i;196 const char *line;197 sigset_t blockset;198199 /* Unfortunately, the history handling is not signal-safe.200 * Hence, we need to ensure that we don't run the cleanup201 * function (which writes the history) while we add to it. */202 if (sigemptyset(&blockset) == -1)203 err(EXIT_FAILURE, "sigemptyset failed");204 for (i = 0; i < LEN(signals); i++)205 sigaddset(&blockset, signals[i]);206207 while ((line = readline(prompt))) {208 /* We output empty lines intentionally. */209 printf("%s\n", line);210 fflush(stdout);211212 if (histfp && *line != '\0') {213 if (sigprocmask(SIG_BLOCK, &blockset, NULL))214 err(EXIT_FAILURE, "signal blocking failed");215 add_history(line);216 if (sigprocmask(SIG_UNBLOCK, &blockset, NULL))217 err(EXIT_FAILURE, "signal unblocking failed");218 }219220 if (single)221 break;222 }223}224225static void226confhist(char *fp, int size)227{228 using_history();229 stifle_history(size);230231 if (!access(fp, F_OK) && read_history(fp))232 err(EXIT_FAILURE, "read_history failed");233}234235static void236confcomp(char *compcmd, int wflag)237{238 int ret;239 size_t cmdlen;240241 if (!(fdtemp = mkstemp(fntemp)))242 err(EXIT_FAILURE, "mkstemp failed");243 if (fchmod(fdtemp, 0600) == -1) /* not manadated by POSIX */244 err(EXIT_FAILURE, "fchmod failed");245246 /* + 2 for the null byte and the pipe character. */247 cmdlen = 2 + strlen(GREPCMD) + strlen(fntemp) + strlen(compcmd);248 if (!(cmdbuf = malloc(cmdlen)))249 err(EXIT_FAILURE, "malloc failed");250251 ret = snprintf(cmdbuf, cmdlen, "%s|" GREPCMD "%s", compcmd, fntemp);252 if (ret < 0)253 err(EXIT_FAILURE, "snprintf failed");254 else if ((size_t)ret >= cmdlen)255 errx(EXIT_FAILURE, "buffer for snprintf is too short");256257 rl_basic_word_break_characters = (wflag) ? " " : "";258 rl_attempted_completion_function = comp;259}260261int262main(int argc, char **argv)263{264 int opt, hsiz, wflag, single;265 char *prompt, *compcmd;266267 single = 0;268 wflag = 0;269 hsiz = 128;270 prompt = "> ";271 compcmd = NULL;272273 while ((opt = getopt(argc, argv, "1wc:p:h:s:")) != -1) {274 switch (opt) {275 case '1':276 single = 1;277 break;278 case 'w':279 wflag = 1;280 break;281 case 'c':282 compcmd = optarg;283 break;284 case 'p':285 prompt = optarg;286 break;287 case 'h':288 histfp = optarg;289 break;290 case 's':291 if (!(hsiz = strtol(optarg, (char **)NULL, 10)))292 err(EXIT_FAILURE, "strtol failed");293 break;294 default:295 usage(*argv);296 }297 }298299 rl_outstream = fout();300 if (histfp)301 confhist(histfp, hsiz);302 if (compcmd)303 confcomp(compcmd, wflag);304 else305 rl_bind_key('\t', rl_insert); /* disable completion */306307 /* setup after initialization to prevent history truncation */308 sethandler();309 if (atexit(onexit))310 err(EXIT_FAILURE, "atexit failed");311312 iloop(single, prompt);313 return EXIT_SUCCESS;314}