fixed for newer perl
[tridge/junkcode.git] / rline / rline.c
1 /* add readline support to any command
2    Copyright 2002 Andrew Tridgell <tridge@samba.org>, June 2002
3
4    released under the GNU General Public License version 2 or later
5 */
6 #define _GNU_SOURCE
7 #include <stdio.h>
8 #include <string.h>
9 #include <stdlib.h>
10 #include <unistd.h>
11 #include <pty.h>
12 #include <fcntl.h>
13 #include <utmp.h>
14 #include <errno.h>
15 #include <readline/readline.h>
16 #include <readline/history.h>
17
18 static void usage(void)
19 {
20         printf("
21 rline version 1.0
22 Copyright 2002 Andrew Tridgell <tridge@samba.org> 
23 Released under the GNU General Public License v2 or later
24
25 Usage:
26   rline COMMAND
27
28 This add readline support to any command line driven program. 
29
30 rline will try to load a completions file from one of the following locations
31 in this order:
32      $RLINE_COMPLETIONS_FILE
33      $RLINE_COMPLETIONS_DIR/COMMAND
34      $HOME/.rline/COMMAND
35      /usr/share/rline/COMMAND
36
37 A completion file consists of one completion per line, with multi-part completions
38 separated by spaces. You can use the special word [FILE] to mean filename completion.
39 ");
40 }
41
42 /* list of command completions */
43 static char **cmd_list;
44 static int num_commands;
45 static int cmd_offset;
46
47 /*
48   load completions from the specified filename
49 */
50 static int load_completions_file(const char *fname)
51 {
52         FILE *f;
53         char line[200];
54
55         f = fopen(fname, "r");
56         if (!f) {
57                 return 0;
58         }
59
60         /* don't bother parsing multi-part completions here, that is done at runtime */
61         while (fgets(line, sizeof(line), f)) {
62                 int len = strlen(line);
63                 if (len == 0) continue;
64                 line[len-1] = 0;
65                 cmd_list = (char **)realloc(cmd_list, sizeof(char *)*(num_commands+1));
66                 if (!cmd_list) break;
67                 if (*line) {
68                         cmd_list[num_commands++] = strdup(line);
69                 }
70         }
71
72         fclose(f);
73         return 1;
74 }
75
76 /* try loading a completions list from varios places */
77 static void load_completions(const char *command)
78 {
79         char *fname;
80         char *p;
81
82         /* take only the last part of the command */
83         if ((p = strrchr(command, '/'))) {
84                 command = p+1;
85         }
86
87         /* try $RLINE_COMPLETIONS_FILE */
88         if ((p = getenv("RLINE_COMPLETIONS_FILE"))) {
89                 if (load_completions_file(p)) return;
90         }
91
92         /* try $RLINE_COMPLETIONS_DIR */
93         if ((p = getenv("RLINE_COMPLETIONS_DIR"))) {
94                 asprintf(&fname, "%s/%s", p, command);
95                 if (load_completions_file(fname)) {
96                         free(fname);
97                         return;
98                 }
99                 free(fname);
100         }
101
102         /* try $HOME/.rline/ */
103         if ((p=getenv("HOME"))) {
104                 asprintf(&fname, "%s/.rline/%s", p, command);
105                 if (load_completions_file(fname)) {
106                         free(fname);
107                         return;
108                 }
109                 free(fname);
110         }
111
112         /* try /usr/share/rline/ */
113         asprintf(&fname, "/usr/share/rline/%s", command);
114         if (load_completions_file(fname)) {
115                 free(fname);
116                 return;
117         }
118         free(fname);
119
120 }
121
122
123 /*
124   command completion generator, including multi-part completions
125  */
126 static char *command_generator(const char *line, int state)
127 {
128         static int idx, len;
129
130         if (!state) {
131                 /* first call */
132                 idx = 0;
133                 len = strlen(line);
134         }
135
136         /* find the next command that matches both the line so far and
137            the next part of the command */
138         for (;idx<num_commands;idx++) {
139                 if (strncmp(rl_line_buffer, cmd_list[idx], cmd_offset) == 0) {
140                         if (strcmp(cmd_list[idx] + cmd_offset, "[FILE]") == 0) {
141                                 /* we want filename completion for this one. This must
142                                    be the last completion */
143                                 rl_filename_completion_desired = 1;
144                                 rl_filename_quoting_desired = 1;
145                                 idx = num_commands;
146                                 return NULL;
147                         }
148                         if (strncmp(line, cmd_list[idx] + cmd_offset, len) == 0) {
149                                 char *p = cmd_list[idx++] + cmd_offset;
150                                 /* return only the current part */
151                                 return strndup(p, strcspn(p, " "));
152                         }
153                 }
154         }
155
156         /* we don't want the filename completer */
157         rl_attempted_completion_over = 1;
158
159         return NULL;
160 }
161
162
163 /* 
164    our completion function, just an interface to our command generator
165  */
166 static char **completion_function(const char *line, int start, int end)
167 {
168         cmd_offset = start;
169
170         return (char **)completion_matches(line, command_generator);
171 }
172
173 /* used by line_handler */
174 static int child_fd;
175
176 /* callback function when readline has a whole line */
177 void line_handler(char *line) 
178 {
179         if (!line) exit(0);
180         /* send the line down the pipe to the command */
181         dprintf(child_fd, "%s\n", line);
182         if (*line) {
183                 /* only add non-empty lines to the history */
184                 add_history(line);
185         }
186 }
187
188
189 /* mirror echo mode from a slave terminal to our terminal */
190 static void mirror_echo_mode(int fd)
191 {  
192         struct termios pterm1, pterm2;
193         
194         if (tcgetattr(fd, &pterm1) != 0) return;
195         if (tcgetattr(0, &pterm2) != 0) return;
196
197         pterm2.c_lflag &= ~ICANON;
198         pterm2.c_lflag &= ~ECHO;
199         pterm2.c_lflag |= ISIG;
200         pterm2.c_cc[VMIN] = 1;
201         pterm2.c_cc[VTIME]=0;
202         pterm2.c_lflag &= ~(ICANON|ISIG|ECHO|ECHONL|ECHOCTL| 
203                             ECHOE|ECHOK|ECHOKE| 
204                             ECHOPRT);
205         if (pterm1.c_lflag) {
206                 pterm2.c_lflag |= ECHO;
207         } else {
208                 pterm2.c_lflag &= ~ECHO;
209         }
210         tcsetattr(0, TCSANOW, &pterm2);
211 }
212
213
214 /* setup the slave side of a pty appropriately */
215 static void setup_terminal(int fd)
216 {
217         struct termios term;
218
219         /* fetch the old settings */
220         if (tcgetattr(fd, &term) != 0) return;
221
222         term.c_cc[VMIN] = 1;
223         term.c_cc[VTIME] = 0;
224         /* we don't want things like echo or other processing */
225         term.c_iflag |= IGNBRK;
226         term.c_lflag &= ~(ICANON|ISIG|ECHO|ECHONL|ECHOCTL| 
227                           ECHOE|ECHOK|ECHOKE| 
228                           ECHOPRT);
229         tcsetattr(fd, TCSANOW, &term);
230 }
231
232 /*
233   main program
234 */
235 int main(int argc, char *argv[])
236 {
237         char *prompt;
238         pid_t pid;
239         struct termios term, *pterm=NULL;
240         int slave_fd;
241         char slave_name[100];
242
243         if (argc < 2 || argv[1][0] == '-') {
244                 usage();
245                 exit(1);
246         }
247
248         /* load the completions list */
249         load_completions(argv[1]);
250
251         if (tcgetattr(0, &term) == 0) {
252                 pterm = &term;
253         }
254
255         /* by using forkpty we give a true pty to the child, which means it should 
256            behave the same as if run from a terminal */
257         pid = forkpty(&child_fd, slave_name, pterm, NULL);
258
259         if (pid == (pid_t)-1) {
260                 perror("forkpty");
261                 exit(1);
262         }
263
264         /* the child just sets up its pty then executes the command */
265         if (pid == 0) {
266                 setup_terminal(0);
267                 execvp(argv[1], argv+1);
268                 /* it failed?? maybe command not found */
269                 perror(argv[1]);
270                 exit(1);
271         }
272
273         slave_fd = open(slave_name, O_RDWR);
274
275         /* initial blank prompt */
276         prompt = strdup("");
277
278         /* install completion handling */
279         rl_already_prompted = 1;
280         rl_attempted_completion_function = completion_function;
281         rl_callback_handler_install(prompt, line_handler);
282
283         /* main loop ... */
284         while (1) {
285                 fd_set fds;
286                 int ret;
287
288                 FD_ZERO(&fds);
289                 FD_SET(0, &fds);
290                 FD_SET(child_fd, &fds);
291
292                 /* wait for some activity */
293                 ret = select(child_fd+1, &fds, NULL, NULL, NULL);
294                 if (ret == -1 && errno == EINTR) continue;
295                 if (ret <= 0) break;
296
297                 /* give any stdin to readline */
298                 if (FD_ISSET(0, &fds)) {
299                         rl_callback_read_char();
300                 }
301
302                 /* data from the program is used to intuit the
303                    prompt. This works surprisingly well */
304                 if (FD_ISSET(child_fd, &fds)) {
305                         char buf[1024];
306                         char *p;
307                         int n = read(child_fd, buf, sizeof(buf)-1);
308                         if (n <= 0) break;
309                         buf[n] = 0;
310
311                         /* send to standard output */
312                         write(1, buf, n);
313
314                         /* work out the next prompt */
315                         p = strrchr(buf, '\n');
316                         if (!p) {
317                                 p = buf;
318                         } else {
319                                 p++;
320                         }
321
322                         /* tell readline about the new prompt */
323                         free(prompt);
324                         prompt = strdup(p);
325
326                         rl_set_prompt(prompt);
327                         rl_on_new_line_with_prompt();
328                         if (slave_fd != -1) {
329                                 mirror_echo_mode(slave_fd);
330                         }
331                 }
332         }
333
334         /* cleanup the terminal */
335         rl_callback_handler_remove();
336
337         return 0;
338 }