1 /* filter.c : spawning a filter and using it to re-write input, output, history and
2 * (C) 2000-2007 Hans Lub
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License , or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; see the file COPYING. If not, write to
16 * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
17 *
18 * You may contact the author by e-mail: hanslub42@gmail.com
19 */
20
21
22
23 /* A filter is an external program run by rlwrap in order to examine
24 and possibly re-write user input, command output, prompts, history
25 entries and completion requests. There can be at most one filter,
26 but this can be a pipeline ('pipeline filter1 : filter2')
27
28 The filter communicates with rlwrap by reading and writing messages
29 on two pipes.
30
31 A message is a byte sequence as follows:
32
33 Tag 1 byte (can be TAG_INPUT, TAG_OUTPUT, TAG_HISTORY,
34 TAG_COMPLETION, TAG_PROMPT, TAG_OUTPUT_OUT_OF_BAND, TAG_ERROR)
35 Length 4 bytes (length of text + closing newline)
36 Text <Length> bytes
37 '\n' (so that the filter can be line buffered without
38 hanging rlwrap)
39
40
41 Communication is synchronous: after sending a message (and only
42 then) rlwrap waits for an answer, which must have the same tag, but
43 may be preceded by one or more "out of band" messages. If the
44 filter is slow or hangs, rlwrap does the same. A filter can (and
45 should) signal an error by using TAG_ERROR (which will terminate
46 rlwrap). Filter output on stderr is displayed normally, but will
47 mess up the display.
48
49 Length may be 0. (Example: If we have a prompt-less command, rlwrap
50 will send an empty TAG_PROMPT message, and the filter can send a
51 fancy prompt back. The converse is also possible, of course)
52
53 A few environment variables are used to inform the filter about
54 file descriptors etc.
55
56 */
57
58
59
60 #include "rlwrap.h"
61
62
63
64
65 static int filter_input_fd = -1;
66 static int filter_output_fd = -1;
67 pid_t filter_pid = 0;
68 static int expected_tag = -1;
69
70
71
72
73 static char*read_from_filter(int tag);
74 static void write_message(int fd, int tag, const char *string, const char *description);
75 static void write_to_filter(int tag, const char *string);
76 static char* tag2description(int tag);
77 static char *read_tagless(void);
78 void handle_out_of_band(int tag, char *message);
79
80
81
mypipe(int filedes[2])82 static void mypipe(int filedes[2]) {
83 int retval;
84 retval = pipe(filedes);
85 if (retval < 0)
86 myerror(FATAL|USE_ERRNO, "Couldn't create pipe");
87 }
88
89
spawn_filter(const char * filter_command)90 void spawn_filter(const char *filter_command) {
91 int input_pipe_fds[2];
92 int output_pipe_fds[2];
93
94 mypipe(input_pipe_fds);
95 filter_input_fd = input_pipe_fds[1]; /* rlwrap writes filter input to this */
96
97 mypipe(output_pipe_fds);
98 filter_output_fd = output_pipe_fds[0]; /* rlwrap reads filter output from here */
99 DPRINTF1(DEBUG_FILTERING, "preparing to spawn filter <%s>", filter_command);
100 assert(!command_pid || signal_handlers_were_installed); /* if there is a command, then signal handlers are installed */
101
102 fflush(NULL);
103 if ((filter_pid = fork()) < 0)
104 myerror(FATAL|USE_ERRNO, "Cannot spawn filter '%s'", filter_command);
105 else if (filter_pid == 0) { /* child */
106 int signals_to_allow[] = {SIGPIPE, SIGCHLD, SIGALRM, SIGUSR1, SIGUSR2, 0};
107 char **argv;
108
109 i_am_filter = TRUE;
110 if (debug)
111 my_fopen(&debug_fp, DEBUG_FILENAME, "a+", "debug log");
112 unblock_signals(signals_to_allow); /* when we run a pager from a filter we want to catch these */
113
114
115 DEBUG_RANDOM_SLEEP;
116 /* set environment for filter (it needs to know at least the file descriptors for its input and output) */
117 DPRINTF1(DEBUG_FILTERING, "getenv{RLWRAP_FILTERDIR} = <%s>", strifnull(getenv("RLWRAP_FILTERDIR")));
118
119 if ((! getenv("RLWRAP_FILTERDIR")) || (! *getenv("RLWRAP_FILTERDIR")))
120 mysetenv("RLWRAP_FILTERDIR", add2strings(DATADIR,"/rlwrap/filters"));
121 mysetenv("PATH", add3strings(getenv("RLWRAP_FILTERDIR"),":",getenv("PATH")));
122 mysetenv("RLWRAP_VERSION", VERSION);
123 mysetenv("RLWRAP_COMMAND_PID", as_string(command_pid));
124 mysetenv("RLWRAP_COMMAND_LINE", command_line);
125 if (impatient_prompt)
126 mysetenv("RLWRAP_IMPATIENT", "1");
127 mysetenv("RLWRAP_INPUT_PIPE_FD", as_string(input_pipe_fds[0]));
128 mysetenv("RLWRAP_OUTPUT_PIPE_FD", as_string(output_pipe_fds[1]));
129 mysetenv("RLWRAP_MASTER_PTY_FD", as_string(master_pty_fd));
130 mysetenv("RLWRAP_BREAK_CHARS", rl_basic_word_break_characters);
131 mysetenv("RLWRAP_DEBUG", as_string(debug));
132
133
134 close(filter_input_fd);
135 close(filter_output_fd);
136
137 /* @@@TODO: split the command in words (possibly quoted when containing spaces). DONT use the shell (|, < and > are never used on filter command lines */
138 if (scan_metacharacters(filter_command, "'|\"><")) { /* if filter_command contains shell metacharacters, let the shell unglue them */
139 char *exec_command = add3strings("exec", " ", filter_command);
140 argv = list4("sh", "-c", exec_command, NULL);
141 DPRINTF1(DEBUG_FILTERING, "exec_command = <%s>", exec_command);
142
143 } else { /* if not, split and feed to execvp directly (cheaper, better error message) */
144 argv = split_with(filter_command, " ");
145 }
146 assert(argv[0]);
147 if(execvp(argv[0], argv) < 0) {
148 char *sorry = add3strings("Cannot exec filter '", argv[0], add2strings("': ", strerror(errno)));
149 write_message(output_pipe_fds[1], TAG_ERROR, sorry, "to stdout"); /* this will kill rlwrap */
150 mymicrosleep(100 * 1000); /* 100 sec for rlwrap to go away should be enough */
151 exit (-1);
152 }
153 assert(!"not reached");
154
155 } else { /* parent */
156 DEBUG_RANDOM_SLEEP;
157 mysignal(SIGPIPE, SIG_IGN, NULL); /* ignore SIGPIPE - we have othere ways to deal with filter death */
158 DPRINTF1(DEBUG_FILTERING, "spawned filter with pid %d", filter_pid);
159 close (input_pipe_fds[0]);
160 close (output_pipe_fds[1]);
161 }
162 }
163
164
kill_filter()165 void kill_filter() {
166 int status;
167 assert (filter_pid && filter_input_fd);
168 close(filter_input_fd); /* filter will see EOF and should exit */
169 myalarm(40); /* give filter 0.04seconds to go away */
170 if(!filter_is_dead && /* filter's SIGCHLD hasn't been caught */
171 waitpid(filter_pid, &status, WNOHANG) < 0 && /* interrupted .. */
172 WTERMSIG(status) == SIGALRM) { /* .. by alarm (and not e.g. by SIGCHLD) */
173 myerror(WARNING|NOERRNO, "filter didn't die - killing it now");
174 }
175 if (filter_pid)
176 kill(filter_pid, SIGKILL); /* do this as a last resort */
177 myalarm(0);
178 }
179
180
181
filters_last_words()182 char *filters_last_words() {
183 assert (filter_is_dead);
184 return read_from_filter(TAG_OUTPUT);
185 }
186
filter_is_interested_in(int tag)187 int filter_is_interested_in(int tag) {
188 static char *interests = NULL;
189 assert(tag <= MAX_INTERESTING_TAG);
190 if (!interests) {
191 char message[MAX_INTERESTING_TAG + 2];
192 int i;
193 mymicrosleep(500); /* Kludge - shouldn't the filter talk first - so we know it's alive? */
194 for (i=0; i <= MAX_INTERESTING_TAG; i++)
195 message[i] = 'n';
196 message[i] = '\0';
197 interests = pass_through_filter(TAG_WHAT_ARE_YOUR_INTERESTS, message);
198 if(interests[TAG_SIGNAL] == 'y')
199 myerror(WARNING|NOERRNO, "this filter handles signals, which means that signals are blocked during filter processing\n"
200 "if the filter hangs, you won't be able to interrupt with e.g. CTRL-C (use kill -9 %d instead) ", getpid());
201
202 }
203 return (interests[tag] == 'y');
204 }
205
206 static int user_frustration_signals[] = {SIGHUP, SIGINT, SIGQUIT, SIGTERM, SIGALRM};
207
208
pass_through_filter(int tag,const char * buffer)209 char *pass_through_filter(int tag, const char *buffer) {
210 char *filtered;
211
212 assert(!out_of_band(tag));
213 if (filter_pid ==0 || (tag < MAX_INTERESTING_TAG && !filter_is_interested_in(tag)))
214 return mysavestring(buffer);
215
216 if (tag == TAG_WHAT_ARE_YOUR_INTERESTS || /* only evaluate next alternative if interests are known */
217 !filter_is_interested_in(TAG_SIGNAL)) /* signal handling filters will get an "unexpected tag" error when the signal arrives during filter processing */
218 unblock_signals(user_frustration_signals); /* allow users to use CTRL-C, but only after uninterruptible_msec */
219
220 DPRINTF3(DEBUG_FILTERING, "to filter (%s, %d bytes) %s", tag2description(tag), (int) strlen(buffer), M(buffer));
221 write_to_filter((expected_tag = tag), buffer);
222 filtered = read_from_filter(tag);
223 DPRINTF3(DEBUG_FILTERING, "from filter (%s, %d bytes) %s", tag2description(tag), (int) strlen(filtered), M(filtered));
224
225 block_all_signals();
226
227 return filtered;
228 }
229
230
231
232
read_from_filter(int tag)233 static char *read_from_filter(int tag) {
234 uint8_t tag8;
235 DEBUG_RANDOM_SLEEP;
236 assert (!out_of_band(tag));
237
238
239 while (read_patiently2(filter_output_fd, &tag8, sizeof(uint8_t), 1000, "from filter"), out_of_band(tag8))
240 handle_out_of_band(tag8, read_tagless());
241 if (tag8 != tag)
242 myerror(FATAL|NOERRNO, "Tag mismatch, expected %s from filter, but got %s", tag2description(tag), tag2description(tag8));
243
244 return read_tagless();
245 }
246
247
read_tagless()248 static char *read_tagless() {
249 uint32_t length32;
250 char *buffer;
251
252 read_patiently2(filter_output_fd, &length32, sizeof(uint32_t), 1000, "from filter");
253 buffer = mymalloc(length32);
254 read_patiently2(filter_output_fd, buffer, length32, 1000,"from filter");
255
256 if (buffer[length32 -1 ] != '\n')
257 myerror(FATAL|USE_ERRNO, "filter output without closing newline");
258 buffer[length32 -1 ] = '\0';
259
260 return buffer;
261 }
262
starts_with(const char * str,const char * prefix)263 static bool starts_with(const char *str, const char *prefix) {
264 return mystrstr(str, prefix) == str;
265 }
266
maybe_tweak_readline(const char * message)267 static void maybe_tweak_readline(const char*message) { /* a bit kludgey, but easy to extend */
268 char **words;
269 if (message[0] != '@')
270 return;
271 words = split_with(message, "::"); /* words and its elements are allocated on the heap */
272 /* parameter checking should be done in the {python,perl} modules - if not, the assertions below may fail */
273 assert(words[1] != NULL);
274 if (starts_with(message, "@rl_completer_word_break_characters::"))
275 rl_completer_word_break_characters = words[1];
276 if (starts_with(message, "@rl_completer_quote_characters::"))
277 rl_completer_quote_characters = words[1];
278 if (starts_with(message, "@rl_filename_completion_desired::"))
279 rl_filename_completion_desired = my_atoi(words[1]);
280 if (starts_with(message, "@rl_variable_bind::")) { /* "@rl_variable_bind::rl_variable_name::value::\n" */
281 assert(words[2] != NULL);
282 rl_variable_bind(words[1], words[2]); /* no need for error handling: readline will complain if necessary */
283 }
284 /* feel free to extend this list (but make sure to modify the {perl,python} modules accordingly! */
285 }
286
handle_out_of_band(int tag,char * message)287 void handle_out_of_band(int tag, char *message) {
288 int split_em_up = FALSE;
289
290 DPRINTF3(DEBUG_FILTERING, "received out-of-band (%s, %d bytes) %s", tag2description(tag),
291 (int) strlen(message), M(message));
292 switch (tag) {
293 case TAG_ERROR:
294 if (expected_tag == TAG_COMPLETION) /* start new line when completing (looks better) */
295 fprintf(stderr, "\n"); /* @@@ error reporting (still) uses buffered I/O */
296 WONTRETURN(myerror(FATAL|NOERRNO, message));
297 case TAG_OUTPUT_OUT_OF_BAND:
298 my_putstr(message);
299 break;
300 case TAG_ADD_TO_COMPLETION_LIST:
301 case TAG_REMOVE_FROM_COMPLETION_LIST:
302 split_em_up = TRUE;
303 break;
304 case TAG_IGNORE:
305 maybe_tweak_readline(message);
306 break;
307 default:
308 WONTRETURN(myerror(FATAL|USE_ERRNO, "out-of-band message with unknown tag %d: <%20s>", tag, message));
309 }
310 if (split_em_up) {
311 char **words = split_with(message, " \n\t");
312 char **plist, *word;
313 for(plist = words;(word = *plist); plist++)
314 if (tag == TAG_ADD_TO_COMPLETION_LIST)
315 add_word_to_completions(word);
316 else
317 remove_word_from_completions(word);
318 free_splitlist(words);
319 }
320 free(message);
321 }
322
323
write_to_filter(int tag,const char * string)324 static void write_to_filter(int tag, const char *string) {
325 write_message(filter_input_fd, tag, string, "to filter");
326 }
327
328
write_message(int fd,int tag,const char * string,const char * description)329 static void write_message(int fd, int tag, const char *string, const char *description) {
330 uint8_t tag8 = tag;
331 uint32_t length32 = strlen(string) + 1;
332 write_patiently2(fd, &tag8, sizeof (uint8_t) , 1000, description);
333 write_patiently2(fd, &length32, sizeof(uint32_t), 1000, description);
334 write_patiently2(fd, string, length32 - 1 , 1000, description);
335 write_patiently2(fd, "\n", 1 , 1000, description);
336 }
337
338
339
340
tag2description(int tag)341 static char* tag2description(int tag) {
342 switch (tag) {
343 case TAG_INPUT: return "INPUT";
344 case TAG_OUTPUT: return "OUTPUT";
345 case TAG_HISTORY: return "HISTORY";
346 case TAG_COMPLETION: return "COMPLETION";
347 case TAG_PROMPT: return "PROMPT";
348 case TAG_HOTKEY: return "HOTKEY";
349 case TAG_SIGNAL: return "SIGNAL";
350 case TAG_WHAT_ARE_YOUR_INTERESTS: return "WHAT_ARE_YOUR_INTERESTS";
351 case TAG_IGNORE: return "TAG_IGNORE";
352 case TAG_ADD_TO_COMPLETION_LIST: return "ADD_TO_COMPLETION_LIST";
353 case TAG_REMOVE_FROM_COMPLETION_LIST:return "REMOVE_FROM_COMPLETION_LIST";
354 case TAG_OUTPUT_OUT_OF_BAND: return "OUTPUT_OUT_OF_BAND";
355 case TAG_ERROR: return "ERROR";
356 default: return as_string(tag);
357 }
358 }
359
360