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
22static char *cmdbuf;
23static char *histfp;
24
25static int fdtemp = -1;
26static char fntemp[] = "/tmp/inputXXXXXX";
27static int signals[] = {SIGINT, SIGTERM, SIGQUIT, SIGHUP};
28
29static void
30usage(char *prog)
31{
32 char *usage = "[-1] [-w] [-c COMMAND] [-p PROMPT] "
33 "[-h HISTORY] [-s HISTSIZE]";
34
35 fprintf(stderr, "USAGE: %s %s\n", basename(prog), usage);
36 exit(EXIT_FAILURE);
37}
38
39static void
40cleanup(void)
41{
42 static short clean;
43
44 if (clean)
45 return; /* don't cleanup twice */
46
47 if (histfp) {
48 if (write_history(histfp))
49 warn("saving history to '%s' failed", histfp);
50 }
51
52 if (fdtemp > 0) {
53 if (close(fdtemp) == -1)
54 warn("close failed for '%s'", fntemp);
55 if (remove(fntemp) == -1)
56 warn("couldn't remove file '%s'", fntemp);
57 }
58
59 clean = 1;
60}
61
62static void
63onexit(void)
64{
65 sigset_t blockset;
66
67 /* cleanup is called atexit(3) and from a signal handler, block
68 * all signals here before invoking cleanup to ensure we are not
69 * interrupted by the signal handler during cleanup. */
70
71 if (sigfillset(&blockset) == -1)
72 err(EXIT_FAILURE, "sigfillset failed");
73 if (sigprocmask(SIG_BLOCK, &blockset, NULL))
74 err(EXIT_FAILURE, "sigprocmask failed");
75
76 cleanup();
77}
78
79static void
80sighandler(int num)
81{
82 (void)num;
83
84 cleanup();
85 exit(EXIT_FAILURE);
86}
87
88static void
89sethandler(void)
90{
91 size_t i;
92 struct sigaction act;
93
94 act.sa_flags = 0;
95 act.sa_handler = sighandler;
96 if (sigemptyset(&act.sa_mask) == -1)
97 err(EXIT_FAILURE, "sigemptyset failed");
98
99 for (i = 0; i < (sizeof(signals) / sizeof(signals[0])); i++) {
100 if (sigaction(signals[i], &act, NULL))
101 err(EXIT_FAILURE, "sigaction failed");
102 }
103}
104
105static FILE *
106fout(void)
107{
108 FILE *out;
109
110 out = stdout;
111 if (isatty(fileno(out)))
112 return out;
113
114 if (!(out = fopen(TTYDEVP, "w")))
115 err(EXIT_FAILURE, "fopen failed");
116 return out;
117}
118
119static char *
120safegrep(const char *pattern, size_t len)
121{
122 if (ftruncate(fdtemp, 0) == -1)
123 err(EXIT_FAILURE, "ftruncate failed");
124 if (lseek(fdtemp, SEEK_SET, 0) == -1)
125 err(EXIT_FAILURE, "lseek failed");
126
127 if (write(fdtemp, pattern, len) == -1 ||
128 write(fdtemp, "\n", 1) == -1)
129 err(EXIT_FAILURE, "write failed");
130
131 return cmdbuf;
132}
133
134static char *
135gencomp(const char *input, int state)
136{
137 ssize_t n;
138 size_t inlen;
139 char *r, *cmd;
140 static FILE *pipe;
141 static char *line;
142 static size_t llen;
143
144 inlen = strlen(input);
145
146 if (!state) {
147 cmd = safegrep(input, inlen);
148 if (!(pipe = popen(cmd, "r")))
149 err(EXIT_FAILURE, "popen failed");
150 }
151
152 while ((n = getline(&line, &llen, pipe)) > 0) {
153 if (strncmp(input, line, inlen))
154 continue;
155 if (line[n - 1] == '\n')
156 line[n - 1] = '\0';
157
158 if (!(r = strdup(line)))
159 err(EXIT_FAILURE, "strdup failed");
160 return r;
161 }
162 if (ferror(pipe))
163 errx(EXIT_FAILURE, "ferror failed");
164
165 if (pclose(pipe) == -1)
166 errx(EXIT_FAILURE, "pclose failed");
167
168 return NULL;
169}
170
171static char **
172comp(const char *text, int start, int end)
173{
174 (void)start;
175 (void)end;
176
177 /* Prevent readline from performing file completions */
178 rl_attempted_completion_over = 1;
179
180 /* Don't append any character to completions */
181 rl_completion_append_character = '\0';
182
183#if RL_VERSION_MAJOR >= 6
184 /* Don't sort completions */
185 rl_sort_completion_matches = 0;
186#endif
187
188 return rl_completion_matches(text, gencomp);
189}
190
191static void
192iloop(int single, char *prompt)
193{
194 const char *line;
195
196 while ((line = readline(prompt))) {
197 /* We output empty lines intentionally. */
198 printf("%s\n", line);
199 fflush(stdout);
200
201 if (histfp && *line != '\0')
202 add_history(line);
203 if (single)
204 break;
205 }
206}
207
208static void
209confhist(char *fp, int size)
210{
211 using_history();
212 stifle_history(size);
213
214 if (!access(fp, F_OK) && read_history(fp))
215 err(EXIT_FAILURE, "read_history failed");
216}
217
218static void
219confcomp(char *compcmd, int wflag)
220{
221 int ret;
222 size_t cmdlen;
223
224 if (!(fdtemp = mkstemp(fntemp)))
225 err(EXIT_FAILURE, "mkstemp failed");
226 if (fchmod(fdtemp, 0600) == -1) /* not manadated by POSIX */
227 err(EXIT_FAILURE, "fchmod failed");
228
229 /* + 2 for the null byte and the pipe character. */
230 cmdlen = 2 + strlen(GREPCMD) + strlen(fntemp) + strlen(compcmd);
231 if (!(cmdbuf = malloc(cmdlen)))
232 err(EXIT_FAILURE, "malloc failed");
233
234 ret = snprintf(cmdbuf, cmdlen, "%s|" GREPCMD "%s", compcmd, fntemp);
235 if (ret < 0)
236 err(EXIT_FAILURE, "snprintf failed");
237 else if ((size_t)ret >= cmdlen)
238 errx(EXIT_FAILURE, "buffer for snprintf is too short");
239
240 rl_basic_word_break_characters = (wflag) ? " " : "";
241 rl_attempted_completion_function = comp;
242}
243
244int
245main(int argc, char **argv)
246{
247 int opt, hsiz, wflag, single;
248 char *prompt, *compcmd;
249
250 single = 0;
251 wflag = 0;
252 hsiz = 128;
253 prompt = "> ";
254 compcmd = NULL;
255
256 while ((opt = getopt(argc, argv, "1wc:p:h:s:")) != -1) {
257 switch (opt) {
258 case '1':
259 single = 1;
260 break;
261 case 'w':
262 wflag = 1;
263 break;
264 case 'c':
265 compcmd = optarg;
266 break;
267 case 'p':
268 prompt = optarg;
269 break;
270 case 'h':
271 histfp = optarg;
272 break;
273 case 's':
274 if (!(hsiz = strtol(optarg, (char **)NULL, 10)))
275 err(EXIT_FAILURE, "strtol failed");
276 break;
277 default:
278 usage(*argv);
279 }
280 }
281
282 rl_outstream = fout();
283 if (histfp)
284 confhist(histfp, hsiz);
285 if (compcmd)
286 confcomp(compcmd, wflag);
287 else
288 rl_bind_key('\t', rl_insert); /* disable completion */
289
290 /* setup after initialization to prevent history truncation */
291 sethandler();
292 if (atexit(onexit))
293 err(EXIT_FAILURE, "atexit failed");
294
295 iloop(single, prompt);
296 return EXIT_SUCCESS;
297}