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>
12
13#include <readline/history.h>
14#include <readline/readline.h>
15
16#include <sys/stat.h>
17#include <sys/types.h>
18
19#define GREPCMD "grep -F -f "
20#define TTYDEVP "/dev/tty"
21#define LEN(X) (sizeof(X) / sizeof(X[0]))
22
23static char *cmdbuf;
24static char *histfp;
25
26static int fdtemp = -1;
27static char fntemp[] = "/tmp/inputXXXXXX";
28static int signals[] = {SIGINT, SIGTERM, SIGQUIT, SIGHUP};
29
30static void
31usage(char *prog)
32{
33 char *usage = "[-1] [-w] [-c COMMAND] [-p PROMPT] "
34 "[-h HISTORY] [-s HISTSIZE]";
35
36 fprintf(stderr, "USAGE: %s %s\n", basename(prog), usage);
37 exit(EXIT_FAILURE);
38}
39
40static void
41cleanup(void)
42{
43 static volatile sig_atomic_t clean;
44
45 if (clean)
46 return; /* don't cleanup twice */
47
48 if (histfp) {
49 if (write_history(histfp))
50 warn("saving history to '%s' failed", histfp);
51 }
52
53 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 }
59
60 clean = 1;
61}
62
63static void
64onexit(void)
65{
66 sigset_t blockset;
67
68 /* cleanup is called atexit(3) and from a signal handler, block
69 * all signals here before invoking cleanup to ensure we are not
70 * interrupted by the signal handler during cleanup. */
71
72 if (sigfillset(&blockset) == -1)
73 err(EXIT_FAILURE, "sigfillset failed");
74 if (sigprocmask(SIG_BLOCK, &blockset, NULL))
75 err(EXIT_FAILURE, "sigprocmask failed");
76
77 cleanup();
78}
79
80static void
81sighandler(int num)
82{
83 (void)num;
84
85 cleanup();
86 exit(EXIT_FAILURE);
87}
88
89static void
90sethandler(void)
91{
92 size_t i;
93 struct sigaction act;
94
95 act.sa_flags = 0;
96 act.sa_handler = sighandler;
97 if (sigemptyset(&act.sa_mask) == -1)
98 err(EXIT_FAILURE, "sigemptyset failed");
99
100 for (i = 0; i < LEN(signals); i++) {
101 if (sigaction(signals[i], &act, NULL))
102 err(EXIT_FAILURE, "sigaction failed");
103 }
104}
105
106static FILE *
107fout(void)
108{
109 FILE *out;
110
111 out = stdout;
112 if (isatty(fileno(out)))
113 return out;
114
115 if (!(out = fopen(TTYDEVP, "w")))
116 err(EXIT_FAILURE, "fopen failed");
117 return out;
118}
119
120static 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");
127
128 if (write(fdtemp, pattern, len) == -1 ||
129 write(fdtemp, "\n", 1) == -1)
130 err(EXIT_FAILURE, "write failed");
131
132 return cmdbuf;
133}
134
135static 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;
144
145 inlen = strlen(input);
146
147 if (!state) {
148 cmd = safegrep(input, inlen);
149 if (!(pipe = popen(cmd, "r")))
150 err(EXIT_FAILURE, "popen failed");
151 }
152
153 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';
158
159 if (!(r = strdup(line)))
160 err(EXIT_FAILURE, "strdup failed");
161 return r;
162 }
163 if (ferror(pipe))
164 errx(EXIT_FAILURE, "ferror failed");
165
166 if (pclose(pipe) == -1)
167 errx(EXIT_FAILURE, "pclose failed");
168
169 return NULL;
170}
171
172static char **
173comp(const char *text, int start, int end)
174{
175 (void)start;
176 (void)end;
177
178 /* Prevent readline from performing file completions */
179 rl_attempted_completion_over = 1;
180
181 /* Don't append any character to completions */
182 rl_completion_append_character = '\0';
183
184#if RL_VERSION_MAJOR >= 6
185 /* Don't sort completions */
186 rl_sort_completion_matches = 0;
187#endif
188
189 return rl_completion_matches(text, gencomp);
190}
191
192static void
193iloop(int single, char *prompt)
194{
195 size_t i;
196 const char *line;
197 sigset_t blockset;
198
199 /* Unfortunately, the history handling is not signal-safe.
200 * Hence, we need to ensure that we don't run the cleanup
201 * 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]);
206
207 while ((line = readline(prompt))) {
208 /* We output empty lines intentionally. */
209 printf("%s\n", line);
210 fflush(stdout);
211
212 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 }
219
220 if (single)
221 break;
222 }
223}
224
225static void
226confhist(char *fp, int size)
227{
228 using_history();
229 stifle_history(size);
230
231 if (!access(fp, F_OK) && read_history(fp))
232 err(EXIT_FAILURE, "read_history failed");
233}
234
235static void
236confcomp(char *compcmd, int wflag)
237{
238 int ret;
239 size_t cmdlen;
240
241 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");
245
246 /* + 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");
250
251 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");
256
257 rl_basic_word_break_characters = (wflag) ? " " : "";
258 rl_attempted_completion_function = comp;
259}
260
261int
262main(int argc, char **argv)
263{
264 int opt, hsiz, wflag, single;
265 char *prompt, *compcmd;
266
267 single = 0;
268 wflag = 0;
269 hsiz = 128;
270 prompt = "> ";
271 compcmd = NULL;
272
273 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 }
298
299 rl_outstream = fout();
300 if (histfp)
301 confhist(histfp, hsiz);
302 if (compcmd)
303 confcomp(compcmd, wflag);
304 else
305 rl_bind_key('\t', rl_insert); /* disable completion */
306
307 /* setup after initialization to prevent history truncation */
308 sethandler();
309 if (atexit(onexit))
310 err(EXIT_FAILURE, "atexit failed");
311
312 iloop(single, prompt);
313 return EXIT_SUCCESS;
314}