It turns out that DODMALLOC was broken when I reorganized busybox.h
[oweals/busybox.git] / cmdedit.c
1 /* vi: set sw=4 ts=4: */
2 /*
3  * Termios command line History and Editting.
4  *
5  * Copyright (c) 1986-2001 may safely be consumed by a BSD or GPL license.
6  * Written by:   Vladimir Oleynik <vodz@usa.net>
7  *
8  * Used ideas:
9  *      Adam Rogoyski    <rogoyski@cs.utexas.edu>
10  *      Dave Cinege      <dcinege@psychosis.com>
11  *      Jakub Jelinek (c) 1995
12  *      Erik Andersen    <andersee@debian.org> (Majorly adjusted for busybox)
13  *
14  * This code is 'as is' with no warranty.
15  *
16  *
17  */
18
19 /*
20    Usage and Known bugs:
21    Terminal key codes are not extensive, and more will probably
22    need to be added. This version was created on Debian GNU/Linux 2.x.
23    Delete, Backspace, Home, End, and the arrow keys were tested
24    to work in an Xterm and console. Ctrl-A also works as Home.
25    Ctrl-E also works as End.
26
27    Small bugs (simple effect):
28    - not true viewing if terminal size (x*y symbols) less
29      size (prompt + editor`s line + 2 symbols)
30  */
31
32
33 //#define TEST
34
35 #include <stdio.h>
36 #include <errno.h>
37 #include <unistd.h>
38 #include <stdlib.h>
39 #include <string.h>
40 #include <sys/ioctl.h>
41 #include <ctype.h>
42 #include <signal.h>
43 #include <limits.h>
44
45 #ifdef BB_FEATURE_SH_TAB_COMPLETION
46 #include <dirent.h>
47 #include <sys/stat.h>
48 #endif
49
50
51 #ifndef TEST
52
53 #include "busybox.h"
54
55 #define D(x)
56
57 #else
58
59 #define BB_FEATURE_SH_COMMAND_EDITING
60 #define BB_FEATURE_SH_TAB_COMPLETION
61 #define BB_FEATURE_SH_USERNAME_COMPLETION
62 #define BB_FEATURE_NONPRINTABLE_INVERSE_PUT
63 #define BB_FEATURE_BASH_STYLE_PROMT
64 #define BB_FEATURE_CLEAN_UP
65
66 #define D(x)  x
67
68 #endif                                                  /* TEST */
69
70 #ifdef BB_FEATURE_SH_COMMAND_EDITING
71
72 #ifndef BB_FEATURE_SH_TAB_COMPLETION
73 #undef  BB_FEATURE_SH_USERNAME_COMPLETION
74 #endif
75
76 #if defined(BB_FEATURE_SH_USERNAME_COMPLETION) || defined(BB_FEATURE_BASH_STYLE_PROMT)
77 #define BB_FEATURE_GETUSERNAME_AND_HOMEDIR
78 #endif
79
80 #ifdef BB_FEATURE_GETUSERNAME_AND_HOMEDIR
81 #ifndef TEST
82 #include "pwd_grp/pwd.h"
83 #else
84 #include <pwd.h>
85 #endif                                                  /* TEST */
86 #endif                                                  /* advanced FEATURES */
87
88
89 #ifdef TEST
90 void *xrealloc(void *old, size_t size)
91 {
92         return realloc(old, size);
93 }
94
95 void *xmalloc(size_t size)
96 {
97         return malloc(size);
98 }
99 char *xstrdup(const char *s)
100 {
101         return strdup(s);
102 }
103
104 void *xcalloc(size_t size, size_t se)
105 {
106         return calloc(size, se);
107 }
108
109 #define error_msg(s, d)                   fprintf(stderr, s, d)
110 #endif
111
112
113 struct history {
114         char *s;
115         struct history *p;
116         struct history *n;
117 };
118
119 /* Maximum length of the linked list for the command line history */
120 static const int MAX_HISTORY = 15;
121
122 /* First element in command line list */
123 static struct history *his_front = NULL;
124
125 /* Last element in command line list */
126 static struct history *his_end = NULL;
127
128
129 /* ED: sparc termios is broken: revert back to old termio handling. */
130
131 #if #cpu(sparc)
132 #      include <termio.h>
133 #      define termios termio
134 #      define setTermSettings(fd,argp) ioctl(fd,TCSETAF,argp)
135 #      define getTermSettings(fd,argp) ioctl(fd,TCGETA,argp)
136 #else
137 #      include <termios.h>
138 #      define setTermSettings(fd,argp) tcsetattr(fd,TCSANOW,argp)
139 #      define getTermSettings(fd,argp) tcgetattr(fd, argp);
140 #endif
141
142 /* Current termio and the previous termio before starting sh */
143 static struct termios initial_settings, new_settings;
144
145
146 #ifndef _POSIX_VDISABLE
147 #define _POSIX_VDISABLE '\0'
148 #endif
149
150
151 static
152 volatile int cmdedit_termw = 80;        /* actual terminal width */
153 static int history_counter = 0; /* Number of commands in history list */
154 static
155 volatile int handlers_sets = 0; /* Set next bites: */
156
157 enum {
158         SET_ATEXIT = 1,                         /* when atexit() has been called and
159                                                                    get euid,uid,gid to fast compare  */
160         SET_TERM_HANDLERS = 2,          /* set many terminates signal handlers */
161         SET_WCHG_HANDLERS = 4,          /* winchg signal handler */
162         SET_RESET_TERM = 8,                     /* if the terminal needs to be reset upon exit */
163 };
164
165
166 static int cmdedit_x;                   /* real x terminal position */
167 static int cmdedit_y;                   /* pseudoreal y terminal position */
168 static int cmdedit_prmt_len;    /* lenght prompt without colores string */
169
170 static int cursor;                              /* required global for signal handler */
171 static int len;                                 /* --- "" - - "" - -"- --""-- --""--- */
172 static char *command_ps;                /* --- "" - - "" - -"- --""-- --""--- */
173 static
174 #ifndef BB_FEATURE_BASH_STYLE_PROMT
175         const
176 #endif
177 char *cmdedit_prompt;                   /* --- "" - - "" - -"- --""-- --""--- */
178
179 /* Link into lash to reset context to 0 on ^C and such */
180 extern unsigned int shell_context;
181
182
183 #ifdef BB_FEATURE_GETUSERNAME_AND_HOMEDIR
184 static char *user_buf = "";
185 static char *home_pwd_buf = "";
186 static int my_euid;
187 #endif
188
189 #ifdef BB_FEATURE_BASH_STYLE_PROMT
190 static char *hostname_buf = "";
191 static int num_ok_lines = 1;
192 #endif
193
194
195 #ifdef  BB_FEATURE_SH_TAB_COMPLETION
196
197 #ifndef BB_FEATURE_GETUSERNAME_AND_HOMEDIR
198 static int my_euid;
199 #endif
200
201 static int my_uid;
202 static int my_gid;
203
204 #endif                                                  /* BB_FEATURE_SH_TAB_COMPLETION */
205
206
207 static void cmdedit_setwidth(int w, int redraw_flg);
208
209 static void win_changed(int nsig)
210 {
211         struct winsize win = { 0, 0, 0, 0 };
212         static __sighandler_t previous_SIGWINCH_handler;        /* for reset */
213
214         /*   emulate      || signal call */
215         if (nsig == -SIGWINCH || nsig == SIGWINCH) {
216                 ioctl(0, TIOCGWINSZ, &win);
217                 if (win.ws_col > 0) {
218                         cmdedit_setwidth(win.ws_col, nsig == SIGWINCH);
219                 } 
220         }
221         /* Unix not all standart in recall signal */
222
223         if (nsig == -SIGWINCH)          /* save previous handler   */
224                 previous_SIGWINCH_handler = signal(SIGWINCH, win_changed);
225         else if (nsig == SIGWINCH)      /* signaled called handler */
226                 signal(SIGWINCH, win_changed);  /* set for next call       */
227         else                                            /* nsig == 0 */
228                 /* set previous handler    */
229                 signal(SIGWINCH, previous_SIGWINCH_handler);    /* reset    */
230 }
231
232 static void cmdedit_reset_term(void)
233 {
234         if ((handlers_sets & SET_RESET_TERM) != 0) {
235                 /* sparc and other have broken termios support: use old termio handling. */
236                 setTermSettings(fileno(stdin), (void *) &initial_settings);
237                 handlers_sets &= ~SET_RESET_TERM;
238         }
239         if ((handlers_sets & SET_WCHG_HANDLERS) != 0) {
240                 /* reset SIGWINCH handler to previous (default) */
241                 win_changed(0);
242                 handlers_sets &= ~SET_WCHG_HANDLERS;
243         }
244         fflush(stdout);
245 #ifdef BB_FEATURE_CLEAN_UP
246         if (his_front) {
247                 struct history *n;
248
249                 //while(his_front!=his_end) {
250                 while (his_front != his_end) {
251                         n = his_front->n;
252                         free(his_front->s);
253                         free(his_front);
254                         his_front = n;
255                 }
256         }
257 #endif
258 }
259
260
261 /* special for recount position for scroll and remove terminal margin effect */
262 static void cmdedit_set_out_char(int next_char)
263 {
264
265         int c = command_ps[cursor];
266
267         if (c == 0)
268                 c = ' ';                                /* destroy end char? */
269 #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT
270         if (!isprint(c)) {                      /* Inverse put non-printable characters */
271                 if (((unsigned char) c) >= 128)
272                         c -= 128;
273                 if (((unsigned char) c) < ' ')
274                         c += '@';
275                 if (c == 127)
276                         c = '?';
277                 printf("\033[7m%c\033[0m", c);
278         } else
279 #endif
280                 putchar(c);
281         if (++cmdedit_x >= cmdedit_termw) {
282                 /* terminal is scrolled down */
283                 cmdedit_y++;
284                 cmdedit_x = 0;
285
286                 if (!next_char)
287                         next_char = ' ';
288                 /* destroy "(auto)margin" */
289                 putchar(next_char);
290                 putchar('\b');
291         }
292         cursor++;
293 }
294
295 /* Move to end line. Bonus: rewrite line from cursor */
296 static void input_end(void)
297 {
298         while (cursor < len)
299                 cmdedit_set_out_char(0);
300 }
301
302 /* Go to the next line */
303 static void goto_new_line(void)
304 {
305         input_end();
306         if (cmdedit_x)
307                 putchar('\n');
308 }
309
310
311 static inline void out1str(const char *s)
312 {
313         fputs(s, stdout);
314 }
315 static inline void beep(void)
316 {
317         putchar('\007');
318 }
319
320 /* Move back one charactor */
321 /* special for slow terminal */
322 static void input_backward(int num)
323 {
324         if (num > cursor)
325                 num = cursor;
326         cursor -= num;                          /* new cursor (in command, not terminal) */
327
328         if (cmdedit_x >= num) {         /* no to up line */
329                 cmdedit_x -= num;
330                 if (num < 4)
331                         while (num-- > 0)
332                                 putchar('\b');
333
334                 else
335                         printf("\033[%dD", num);
336         } else {
337                 int count_y;
338
339                 if (cmdedit_x) {
340                         putchar('\r');          /* back to first terminal pos.  */
341                         num -= cmdedit_x;       /* set previous backward        */
342                 }
343                 count_y = 1 + num / cmdedit_termw;
344                 printf("\033[%dA", count_y);
345                 cmdedit_y -= count_y;
346                 /*  require  forward  after  uping   */
347                 cmdedit_x = cmdedit_termw * count_y - num;
348                 printf("\033[%dC", cmdedit_x);  /* set term cursor   */
349         }
350 }
351
352 static void put_prompt(void)
353 {
354         out1str(cmdedit_prompt);
355         cmdedit_x = cmdedit_prmt_len;   /* count real x terminal position */
356         cursor = 0;
357 }
358
359 #ifdef BB_FEATURE_BASH_STYLE_PROMT
360 static void
361 add_to_prompt(char **prmt_mem_ptr, int *alm, int *prmt_len,
362                           const char *addb)
363 {
364         int l = strlen(addb);
365
366         *prmt_len += l;
367         if (*alm < (*prmt_len) + 1) {
368                 *alm = (*prmt_len) + 1;
369                 *prmt_mem_ptr = xrealloc(*prmt_mem_ptr, *alm);
370         }
371         strcat(*prmt_mem_ptr, addb);
372 }
373 #endif
374
375 static void parse_prompt(const char *prmt_ptr)
376 {
377 #ifdef BB_FEATURE_BASH_STYLE_PROMT
378         int alm = strlen(prmt_ptr) + 1; /* supposedly require memory */
379         int prmt_len = 0;
380         int sub_len = 0;
381         int flg_not_length = '[';
382         char *prmt_mem_ptr = xstrdup(prmt_ptr);
383         char pwd_buf[PATH_MAX + 1];
384         char buf[16];
385         int c;
386
387         pwd_buf[0] = 0;
388         *prmt_mem_ptr = 0;
389
390         while (*prmt_ptr) {
391                 c = *prmt_ptr++;
392                 if (c == '\\') {
393                         c = *prmt_ptr;
394                         if (c == 0)
395                                 break;
396                         prmt_ptr++;
397                         switch (c) {
398                         case 'u':
399                                 add_to_prompt(&prmt_mem_ptr, &alm, &prmt_len, user_buf);
400                                 continue;
401                         case 'h':
402                                 if (hostname_buf[0] == 0) {
403                                         hostname_buf = xcalloc(256, 1);
404                                         if (gethostname(hostname_buf, 255) < 0) {
405                                                 strcpy(hostname_buf, "?");
406                                         } else {
407                                                 char *s = strchr(hostname_buf, '.');
408
409                                                 if (s)
410                                                         *s = 0;
411                                         }
412                                 }
413                                 add_to_prompt(&prmt_mem_ptr, &alm, &prmt_len,
414                                                           hostname_buf);
415                                 continue;
416                         case '$':
417                                 c = my_euid == 0 ? '#' : '$';
418                                 break;
419                         case 'w':
420                                 if (pwd_buf[0] == 0) {
421                                         int l;
422
423                                         getcwd(pwd_buf, PATH_MAX);
424                                         l = strlen(home_pwd_buf);
425                                         if (home_pwd_buf[0] != 0 &&
426                                                 strncmp(home_pwd_buf, pwd_buf, l) == 0) {
427                                                 strcpy(pwd_buf + 1, pwd_buf + l);
428                                                 pwd_buf[0] = '~';
429                                         }
430                                 }
431                                 add_to_prompt(&prmt_mem_ptr, &alm, &prmt_len, pwd_buf);
432                                 continue;
433                         case '!':
434                                 snprintf(buf, sizeof(buf), "%d", num_ok_lines);
435                                 add_to_prompt(&prmt_mem_ptr, &alm, &prmt_len, buf);
436                                 continue;
437                         case 'e':
438                         case 'E':                       /* \e \E = \033 */
439                                 c = '\033';
440                                 break;
441                         case 'x':
442                         case 'X':
443                         case '0':
444                         case '1':
445                         case '2':
446                         case '3':
447                         case '4':
448                         case '5':
449                         case '6':
450                         case '7':{
451                                 int l;
452                                 int ho = 0;
453                                 char *eho;
454
455                                 if (c == 'X')
456                                         c = 'x';
457
458                                 for (l = 0; l < 3;) {
459
460                                         buf[l++] = *prmt_ptr;
461                                         buf[l] = 0;
462                                         ho = strtol(buf, &eho, c == 'x' ? 16 : 8);
463                                         if (ho > UCHAR_MAX || (eho - buf) < l) {
464                                                 l--;
465                                                 break;
466                                         }
467                                         prmt_ptr++;
468                                 }
469                                 buf[l] = 0;
470                                 ho = strtol(buf, 0, c == 'x' ? 16 : 8);
471                                 c = ho == 0 ? '?' : (char) ho;
472                                 break;
473                         }
474                         case '[':
475                         case ']':
476                                 if (c == flg_not_length) {
477                                         flg_not_length = flg_not_length == '[' ? ']' : '[';
478                                         continue;
479                                 }
480                                 break;
481                         }
482                 }
483                 buf[0] = c;
484                 buf[1] = 0;
485                 add_to_prompt(&prmt_mem_ptr, &alm, &prmt_len, buf);
486                 if (flg_not_length == ']')
487                         sub_len++;
488         }
489         cmdedit_prmt_len = prmt_len - sub_len;
490         cmdedit_prompt = prmt_mem_ptr;
491 #else
492         cmdedit_prompt = prmt_ptr;
493         cmdedit_prmt_len = strlen(prmt_ptr);
494 #endif
495         put_prompt();
496 }
497
498
499 /* draw promt, editor line, and clear tail */
500 static void redraw(int y, int back_cursor)
501 {
502         if (y > 0)                                      /* up to start y */
503                 printf("\033[%dA", y);
504         cmdedit_y = 0;                          /* new quasireal y */
505         putchar('\r');
506         put_prompt();
507         input_end();                            /* rewrite */
508         printf("\033[J");                       /* destroy tail after cursor */
509         input_backward(back_cursor);
510 }
511
512 /* Delete the char in front of the cursor */
513 static void input_delete(void)
514 {
515         int j = cursor;
516
517         if (j == len)
518                 return;
519
520         strcpy(command_ps + j, command_ps + j + 1);
521         len--;
522         input_end();                            /* rewtite new line */
523         cmdedit_set_out_char(0);        /* destroy end char */
524         input_backward(cursor - j);     /* back to old pos cursor */
525 }
526
527 /* Delete the char in back of the cursor */
528 static void input_backspace(void)
529 {
530         if (cursor > 0) {
531                 input_backward(1);
532                 input_delete();
533         }
534 }
535
536
537 /* Move forward one charactor */
538 static void input_forward(void)
539 {
540         if (cursor < len)
541                 cmdedit_set_out_char(command_ps[cursor + 1]);
542 }
543
544
545 static void clean_up_and_die(int sig)
546 {
547         goto_new_line();
548         if (sig != SIGINT)
549                 exit(EXIT_SUCCESS);             /* cmdedit_reset_term() called in atexit */
550         cmdedit_reset_term();
551 }
552
553 static void cmdedit_setwidth(int w, int redraw_flg)
554 {
555         cmdedit_termw = cmdedit_prmt_len + 2;
556         if (w <= cmdedit_termw) {
557                 cmdedit_termw = cmdedit_termw % w;
558         }
559         if (w > cmdedit_termw) {
560
561                 cmdedit_termw = w;
562
563                 if (redraw_flg) {
564                         /* new y for current cursor */
565                         int new_y = (cursor + cmdedit_prmt_len) / w;
566
567                         /* redraw */
568                         redraw((new_y >= cmdedit_y ? new_y : cmdedit_y), len - cursor);
569                         fflush(stdout);
570                 }
571         } 
572 }
573
574 extern void cmdedit_init(void)
575 {
576         if ((handlers_sets & SET_WCHG_HANDLERS) == 0) {
577                 /* emulate usage handler to set handler and call yours work */
578                 win_changed(-SIGWINCH);
579                 handlers_sets |= SET_WCHG_HANDLERS;
580         }
581
582         if ((handlers_sets & SET_ATEXIT) == 0) {
583 #ifdef BB_FEATURE_GETUSERNAME_AND_HOMEDIR
584                 struct passwd *entry;
585
586                 my_euid = geteuid();
587                 entry = getpwuid(my_euid);
588                 if (entry) {
589                         user_buf = xstrdup(entry->pw_name);
590                         home_pwd_buf = xstrdup(entry->pw_dir);
591                 }
592 #endif
593
594 #ifdef  BB_FEATURE_SH_TAB_COMPLETION
595
596 #ifndef BB_FEATURE_GETUSERNAME_AND_HOMEDIR
597                 my_euid = geteuid();
598 #endif
599                 my_uid = getuid();
600                 my_gid = getgid();
601 #endif                                                  /* BB_FEATURE_SH_TAB_COMPLETION */
602                 handlers_sets |= SET_ATEXIT;
603                 atexit(cmdedit_reset_term);     /* be sure to do this only once */
604         }
605
606         if ((handlers_sets & SET_TERM_HANDLERS) == 0) {
607                 signal(SIGKILL, clean_up_and_die);
608                 signal(SIGINT, clean_up_and_die);
609                 signal(SIGQUIT, clean_up_and_die);
610                 signal(SIGTERM, clean_up_and_die);
611                 handlers_sets |= SET_TERM_HANDLERS;
612         }
613
614 }
615
616 #ifdef BB_FEATURE_SH_TAB_COMPLETION
617
618 static int is_execute(const struct stat *st)
619 {
620         if ((!my_euid && (st->st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) ||
621                 (my_uid == st->st_uid && (st->st_mode & S_IXUSR)) ||
622                 (my_gid == st->st_gid && (st->st_mode & S_IXGRP)) ||
623                 (st->st_mode & S_IXOTH)) return TRUE;
624         return FALSE;
625 }
626
627 #ifdef BB_FEATURE_SH_USERNAME_COMPLETION
628
629 static char **username_tab_completion(char *ud, int *num_matches)
630 {
631         struct passwd *entry;
632         int userlen;
633         char *temp;
634
635
636         ud++;                                           /* ~user/... to user/... */
637         userlen = strlen(ud);
638
639         if (num_matches == 0) {         /* "~/..." or "~user/..." */
640                 char *sav_ud = ud - 1;
641                 char *home = 0;
642
643                 if (*ud == '/') {               /* "~/..."     */
644                         home = home_pwd_buf;
645                 } else {
646                         /* "~user/..." */
647                         temp = strchr(ud, '/');
648                         *temp = 0;                      /* ~user\0 */
649                         entry = getpwnam(ud);
650                         *temp = '/';            /* restore ~user/... */
651                         ud = temp;
652                         if (entry)
653                                 home = entry->pw_dir;
654                 }
655                 if (home) {
656                         if ((userlen + strlen(home) + 1) < BUFSIZ) {
657                                 char temp2[BUFSIZ];     /* argument size */
658
659                                 /* /home/user/... */
660                                 sprintf(temp2, "%s%s", home, ud);
661                                 strcpy(sav_ud, temp2);
662                         }
663                 }
664                 return 0;                               /* void, result save to argument :-) */
665         } else {
666                 /* "~[^/]*" */
667                 char **matches = (char **) NULL;
668                 int nm = 0;
669
670                 setpwent();
671
672                 while ((entry = getpwent()) != NULL) {
673                         /* Null usernames should result in all users as possible completions. */
674                         if ( /*!userlen || */ !strncmp(ud, entry->pw_name, userlen)) {
675
676                                 temp = xmalloc(3 + strlen(entry->pw_name));
677                                 sprintf(temp, "~%s/", entry->pw_name);
678                                 matches = xrealloc(matches, (nm + 1) * sizeof(char *));
679
680                                 matches[nm++] = temp;
681                         }
682                 }
683
684                 endpwent();
685                 (*num_matches) = nm;
686                 return (matches);
687         }
688 }
689 #endif                                                  /* BB_FEATURE_SH_USERNAME_COMPLETION */
690
691 enum {
692         FIND_EXE_ONLY = 0,
693         FIND_DIR_ONLY = 1,
694         FIND_FILE_ONLY = 2,
695 };
696
697 static int path_parse(char ***p, int flags)
698 {
699         int npth;
700         char *tmp;
701         char *pth;
702
703         /* if not setenv PATH variable, to search cur dir "." */
704         if (flags != FIND_EXE_ONLY || (pth = getenv("PATH")) == 0 ||
705                 /* PATH=<empty> or PATH=:<empty> */
706                 *pth == 0 || (*pth == ':' && *(pth + 1) == 0)) {
707                 return 1;
708         }
709
710         tmp = pth;
711         npth = 0;
712
713         for (;;) {
714                 npth++;                                 /* count words is + 1 count ':' */
715                 tmp = strchr(tmp, ':');
716                 if (tmp) {
717                         if (*++tmp == 0)
718                                 break;                  /* :<empty> */
719                 } else
720                         break;
721         }
722
723         *p = xmalloc(npth * sizeof(char *));
724
725         tmp = pth;
726         (*p)[0] = xstrdup(tmp);
727         npth = 1;                                       /* count words is + 1 count ':' */
728
729         for (;;) {
730                 tmp = strchr(tmp, ':');
731                 if (tmp) {
732                         (*p)[0][(tmp - pth)] = 0;       /* ':' -> '\0' */
733                         if (*++tmp == 0)
734                                 break;                  /* :<empty> */
735                 } else
736                         break;
737                 (*p)[npth++] = &(*p)[0][(tmp - pth)];   /* p[next]=p[0][&'\0'+1] */
738         }
739
740         return npth;
741 }
742
743 static char *add_quote_for_spec_chars(char *found)
744 {
745         int l = 0;
746         char *s = xmalloc((strlen(found) + 1) * 2);
747
748         while (*found) {
749                 if (strchr(" `\"#$%^&*()=+{}[]:;\'|\\<>", *found))
750                         s[l++] = '\\';
751                 s[l++] = *found++;
752         }
753         s[l] = 0;
754         return s;
755 }
756
757 static char **exe_n_cwd_tab_completion(char *command, int *num_matches,
758                                                                            int type)
759 {
760
761         char **matches = 0;
762         DIR *dir;
763         struct dirent *next;
764         char dirbuf[BUFSIZ];
765         int nm = *num_matches;
766         struct stat st;
767         char *path1[1];
768         char **paths = path1;
769         int npaths;
770         int i;
771         char found[BUFSIZ + 4 + PATH_MAX];
772         char *pfind = strrchr(command, '/');
773
774         path1[0] = ".";
775
776         if (pfind == NULL) {
777                 /* no dir, if flags==EXE_ONLY - get paths, else "." */
778                 npaths = path_parse(&paths, type);
779                 pfind = command;
780         } else {
781                 /* with dir */
782                 /* save for change */
783                 strcpy(dirbuf, command);
784                 /* set dir only */
785                 dirbuf[(pfind - command) + 1] = 0;
786 #ifdef BB_FEATURE_SH_USERNAME_COMPLETION
787                 if (dirbuf[0] == '~')   /* ~/... or ~user/... */
788                         username_tab_completion(dirbuf, 0);
789 #endif
790                 /* "strip" dirname in command */
791                 pfind++;
792
793                 paths[0] = dirbuf;
794                 npaths = 1;                             /* only 1 dir */
795         }
796
797         for (i = 0; i < npaths; i++) {
798
799                 dir = opendir(paths[i]);
800                 if (!dir)                               /* Don't print an error */
801                         continue;
802
803                 while ((next = readdir(dir)) != NULL) {
804                         const char *str_merge = "%s/%s";
805                         char *str_found = next->d_name;
806
807                         /* matched ? */
808                         if (strncmp(str_found, pfind, strlen(pfind)))
809                                 continue;
810                         /* not see .name without .match */
811                         if (*str_found == '.' && *pfind == 0) {
812                                 if (*paths[i] == '/' && paths[i][1] == 0
813                                         && str_found[1] == 0) str_found = "";   /* only "/" */
814                                 else
815                                         continue;
816                         }
817                         if (paths[i][strlen(paths[i]) - 1] == '/')
818                                 str_merge = "%s%s";
819                         sprintf(found, str_merge, paths[i], str_found);
820                         /* hmm, remover in progress? */
821                         if (stat(found, &st) < 0)
822                                 continue;
823                         /* find with dirs ? */
824                         if (paths[i] != dirbuf)
825                                 strcpy(found, next->d_name);    /* only name */
826                         if (S_ISDIR(st.st_mode)) {
827                                 /* name is directory      */
828                                 /* algorithmic only "/" ? */
829                                 if (*str_found)
830                                         strcat(found, "/");
831                                 str_found = add_quote_for_spec_chars(found);
832                         } else {
833                                 /* not put found file if search only dirs for cd */
834                                 if (type == FIND_DIR_ONLY)
835                                         continue;
836                                 str_found = add_quote_for_spec_chars(found);
837                                 if (type == FIND_FILE_ONLY ||
838                                         (type == FIND_EXE_ONLY && is_execute(&st) == TRUE))
839                                         strcat(str_found, " ");
840                         }
841                         /* Add it to the list */
842                         matches = xrealloc(matches, (nm + 1) * sizeof(char *));
843
844                         matches[nm++] = str_found;
845                 }
846                 closedir(dir);
847         }
848         if (paths != path1) {
849                 free(paths[0]);                 /* allocated memory only in first member */
850                 free(paths);
851         }
852         *num_matches = nm;
853         return (matches);
854 }
855
856 static int match_compare(const void *a, const void *b)
857 {
858         return strcmp(*(char **) a, *(char **) b);
859 }
860
861
862
863 #define QUOT    (UCHAR_MAX+1)
864
865 #define collapse_pos(is, in) { \
866         memcpy(int_buf+is, int_buf+in, (BUFSIZ+1-is-in)*sizeof(int)); \
867         memcpy(pos_buf+is, pos_buf+in, (BUFSIZ+1-is-in)*sizeof(int)); }
868
869 static int find_match(char *matchBuf, int *len_with_quotes)
870 {
871         int i, j;
872         int command_mode;
873         int c, c2;
874         int int_buf[BUFSIZ + 1];
875         int pos_buf[BUFSIZ + 1];
876
877         /* set to integer dimension characters and own positions */
878         for (i = 0;; i++) {
879                 int_buf[i] = (int) ((unsigned char) matchBuf[i]);
880                 if (int_buf[i] == 0) {
881                         pos_buf[i] = -1;        /* indicator end line */
882                         break;
883                 } else
884                         pos_buf[i] = i;
885         }
886
887         /* mask \+symbol and convert '\t' to ' ' */
888         for (i = j = 0; matchBuf[i]; i++, j++)
889                 if (matchBuf[i] == '\\') {
890                         collapse_pos(j, j + 1);
891                         int_buf[j] |= QUOT;
892                         i++;
893 #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT
894                         if (matchBuf[i] == '\t')        /* algorithm equivalent */
895                                 int_buf[j] = ' ' | QUOT;
896 #endif
897                 }
898 #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT
899                 else if (matchBuf[i] == '\t')
900                         int_buf[j] = ' ';
901 #endif
902
903         /* mask "symbols" or 'symbols' */
904         c2 = 0;
905         for (i = 0; int_buf[i]; i++) {
906                 c = int_buf[i];
907                 if (c == '\'' || c == '"') {
908                         if (c2 == 0)
909                                 c2 = c;
910                         else {
911                                 if (c == c2)
912                                         c2 = 0;
913                                 else
914                                         int_buf[i] |= QUOT;
915                         }
916                 } else if (c2 != 0 && c != '$')
917                         int_buf[i] |= QUOT;
918         }
919
920         /* skip commands with arguments if line have commands delimiters */
921         /* ';' ';;' '&' '|' '&&' '||' but `>&' `<&' `>|' */
922         for (i = 0; int_buf[i]; i++) {
923                 c = int_buf[i];
924                 c2 = int_buf[i + 1];
925                 j = i ? int_buf[i - 1] : -1;
926                 command_mode = 0;
927                 if (c == ';' || c == '&' || c == '|') {
928                         command_mode = 1 + (c == c2);
929                         if (c == '&') {
930                                 if (j == '>' || j == '<')
931                                         command_mode = 0;
932                         } else if (c == '|' && j == '>')
933                                 command_mode = 0;
934                 }
935                 if (command_mode) {
936                         collapse_pos(0, i + command_mode);
937                         i = -1;                         /* hack incremet */
938                 }
939         }
940         /* collapse `command...` */
941         for (i = 0; int_buf[i]; i++)
942                 if (int_buf[i] == '`') {
943                         for (j = i + 1; int_buf[j]; j++)
944                                 if (int_buf[j] == '`') {
945                                         collapse_pos(i, j + 1);
946                                         j = 0;
947                                         break;
948                                 }
949                         if (j) {
950                                 /* not found close ` - command mode, collapse all previous */
951                                 collapse_pos(0, i + 1);
952                                 break;
953                         } else
954                                 i--;                    /* hack incremet */
955                 }
956
957         /* collapse (command...(command...)...) or {command...{command...}...} */
958         c = 0;                                          /* "recursive" level */
959         c2 = 0;
960         for (i = 0; int_buf[i]; i++)
961                 if (int_buf[i] == '(' || int_buf[i] == '{') {
962                         if (int_buf[i] == '(')
963                                 c++;
964                         else
965                                 c2++;
966                         collapse_pos(0, i + 1);
967                         i = -1;                         /* hack incremet */
968                 }
969         for (i = 0; pos_buf[i] >= 0 && (c > 0 || c2 > 0); i++)
970                 if ((int_buf[i] == ')' && c > 0) || (int_buf[i] == '}' && c2 > 0)) {
971                         if (int_buf[i] == ')')
972                                 c--;
973                         else
974                                 c2--;
975                         collapse_pos(0, i + 1);
976                         i = -1;                         /* hack incremet */
977                 }
978
979         /* skip first not quote space */
980         for (i = 0; int_buf[i]; i++)
981                 if (int_buf[i] != ' ')
982                         break;
983         if (i)
984                 collapse_pos(0, i);
985
986         /* set find mode for completion */
987         command_mode = FIND_EXE_ONLY;
988         for (i = 0; int_buf[i]; i++)
989                 if (int_buf[i] == ' ' || int_buf[i] == '<' || int_buf[i] == '>') {
990                         if (int_buf[i] == ' ' && command_mode == FIND_EXE_ONLY
991                                 && strncmp(&matchBuf[pos_buf[0]], "cd", 2) == 0)
992                                 command_mode = FIND_DIR_ONLY;
993                         else {
994                                 command_mode = FIND_FILE_ONLY;
995                                 break;
996                         }
997                 }
998         /* "strlen" */
999         for (i = 0; int_buf[i]; i++);
1000         /* find last word */
1001         for (--i; i >= 0; i--) {
1002                 c = int_buf[i];
1003                 if (c == ' ' || c == '<' || c == '>' || c == '|' || c == '&') {
1004                         collapse_pos(0, i + 1);
1005                         break;
1006                 }
1007         }
1008         /* skip first not quoted '\'' or '"' */
1009         for (i = 0; int_buf[i] == '\'' || int_buf[i] == '"'; i++);
1010         /* collapse quote or unquote // or /~ */
1011         while ((int_buf[i] & ~QUOT) == '/' && (
1012                                                                                    (int_buf[i + 1] & ~QUOT) == '/'
1013                                                                                    || (int_buf[i + 1] & ~QUOT) ==
1014                                                                                    '~')) i++;
1015         if (i)
1016                 collapse_pos(0, i);
1017
1018         /* set only match and destroy quotes */
1019         j = 0;
1020         for (i = 0; pos_buf[i] >= 0; i++) {
1021                 matchBuf[i] = matchBuf[pos_buf[i]];
1022                 j = pos_buf[i] + 1;
1023         }
1024         matchBuf[i] = 0;
1025         /* old lenght matchBuf with quotes symbols */
1026         *len_with_quotes = j ? j - pos_buf[0] : 0;
1027
1028         return command_mode;
1029 }
1030
1031
1032 static void input_tab(int *lastWasTab)
1033 {
1034         /* Do TAB completion */
1035         static int num_matches;
1036         static char **matches;
1037
1038         if (lastWasTab == 0) {          /* free all memory */
1039                 if (matches) {
1040                         while (num_matches > 0)
1041                                 free(matches[--num_matches]);
1042                         free(matches);
1043                         matches = (char **) NULL;
1044                 }
1045                 return;
1046         }
1047         if (*lastWasTab == FALSE) {
1048
1049                 char *tmp;
1050                 int len_found;
1051                 char matchBuf[BUFSIZ];
1052                 int find_type;
1053                 int recalc_pos;
1054
1055                 *lastWasTab = TRUE;             /* flop trigger */
1056
1057                 /* Make a local copy of the string -- up
1058                  * to the position of the cursor */
1059                 tmp = strncpy(matchBuf, command_ps, cursor);
1060                 tmp[cursor] = 0;
1061
1062                 find_type = find_match(matchBuf, &recalc_pos);
1063
1064                 /* Free up any memory already allocated */
1065                 input_tab(0);
1066
1067 #ifdef BB_FEATURE_SH_USERNAME_COMPLETION
1068                 /* If the word starts with `~' and there is no slash in the word,
1069                  * then try completing this word as a username. */
1070
1071                 if (matchBuf[0] == '~' && strchr(matchBuf, '/') == 0)
1072                         matches = username_tab_completion(matchBuf, &num_matches);
1073 #endif
1074                 /* Try to match any executable in our path and everything
1075                  * in the current working directory that matches.  */
1076                 if (!matches)
1077                         matches =
1078                                 exe_n_cwd_tab_completion(matchBuf, &num_matches,
1079                                                                                  find_type);
1080
1081                 /* Did we find exactly one match? */
1082                 if (!matches || num_matches > 1) {
1083                         char *tmp1;
1084
1085                         beep();
1086                         if (!matches)
1087                                 return;                 /* not found */
1088                         /* sort */
1089                         qsort(matches, num_matches, sizeof(char *), match_compare);
1090
1091                         /* find minimal match */
1092                         tmp = xstrdup(matches[0]);
1093                         for (tmp1 = tmp; *tmp1; tmp1++)
1094                                 for (len_found = 1; len_found < num_matches; len_found++)
1095                                         if (matches[len_found][(tmp1 - tmp)] != *tmp1) {
1096                                                 *tmp1 = 0;
1097                                                 break;
1098                                         }
1099                         if (*tmp == 0) {        /* have unique */
1100                                 free(tmp);
1101                                 return;
1102                         }
1103                 } else {                                /* one match */
1104                         tmp = matches[0];
1105                         /* for next completion current found */
1106                         *lastWasTab = FALSE;
1107                 }
1108
1109                 len_found = strlen(tmp);
1110                 /* have space to placed match? */
1111                 if ((len_found - strlen(matchBuf) + len) < BUFSIZ) {
1112
1113                         /* before word for match   */
1114                         command_ps[cursor - recalc_pos] = 0;
1115                         /* save   tail line        */
1116                         strcpy(matchBuf, command_ps + cursor);
1117                         /* add    match            */
1118                         strcat(command_ps, tmp);
1119                         /* add    tail             */
1120                         strcat(command_ps, matchBuf);
1121                         /* back to begin word for match    */
1122                         input_backward(recalc_pos);
1123                         /* new pos                         */
1124                         recalc_pos = cursor + len_found;
1125                         /* new len                         */
1126                         len = strlen(command_ps);
1127                         /* write out the matched command   */
1128                         input_end();
1129                         input_backward(cursor - recalc_pos);
1130                 }
1131                 if (tmp != matches[0])
1132                         free(tmp);
1133         } else {
1134                 /* Ok -- the last char was a TAB.  Since they
1135                  * just hit TAB again, print a list of all the
1136                  * available choices... */
1137                 if (matches && num_matches > 0) {
1138                         int i, col, l;
1139                         int sav_cursor = cursor;        /* change goto_new_line() */
1140
1141                         /* Go to the next line */
1142                         goto_new_line();
1143                         for (i = 0, col = 0; i < num_matches; i++) {
1144                                 l = strlen(matches[i]);
1145                                 if (l < 14)
1146                                         l = 14;
1147                                 printf("%-14s  ", matches[i]);
1148                                 if ((l += 2) > 16)
1149                                         while (l % 16) {
1150                                                 putchar(' ');
1151                                                 l++;
1152                                         }
1153                                 col += l;
1154                                 col -= (col / cmdedit_termw) * cmdedit_termw;
1155                                 if (col > 60 && matches[i + 1] != NULL) {
1156                                         putchar('\n');
1157                                         col = 0;
1158                                 }
1159                         }
1160                         /* Go to the next line and rewrite */
1161                         putchar('\n');
1162                         redraw(0, len - sav_cursor);
1163                 }
1164         }
1165 }
1166 #endif                                                  /* BB_FEATURE_SH_TAB_COMPLETION */
1167
1168 static void get_previous_history(struct history **hp, struct history *p)
1169 {
1170         if ((*hp)->s)
1171                 free((*hp)->s);
1172         (*hp)->s = xstrdup(command_ps);
1173         *hp = p;
1174 }
1175
1176 static inline void get_next_history(struct history **hp)
1177 {
1178         get_previous_history(hp, (*hp)->n);
1179 }
1180
1181 enum {
1182         ESC = 27,
1183         DEL = 127,
1184 };
1185
1186
1187 /*
1188  * This function is used to grab a character buffer
1189  * from the input file descriptor and allows you to
1190  * a string with full command editing (sortof like
1191  * a mini readline).
1192  *
1193  * The following standard commands are not implemented:
1194  * ESC-b -- Move back one word
1195  * ESC-f -- Move forward one word
1196  * ESC-d -- Delete back one word
1197  * ESC-h -- Delete forward one word
1198  * CTL-t -- Transpose two characters
1199  *
1200  * Furthermore, the "vi" command editing keys are not implemented.
1201  *
1202  */
1203 extern void cmdedit_read_input(char *prompt, char command[BUFSIZ])
1204 {
1205
1206         int inputFd = fileno(stdin);
1207
1208         int break_out = 0;
1209         int lastWasTab = FALSE;
1210         char c = 0;
1211         struct history *hp = his_end;
1212
1213         /* prepare before init handlers */
1214         cmdedit_y = 0;                          /* quasireal y, not true work if line > xt*yt */
1215         len = 0;
1216         command_ps = command;
1217
1218         if (new_settings.c_cc[VMIN] == 0) {     /* first call */
1219
1220                 getTermSettings(inputFd, (void *) &initial_settings);
1221                 memcpy(&new_settings, &initial_settings, sizeof(struct termios));
1222
1223                 new_settings.c_cc[VMIN] = 1;
1224                 new_settings.c_cc[VTIME] = 0;
1225                 new_settings.c_cc[VINTR] = _POSIX_VDISABLE;     /* Turn off CTRL-C, so we can trap it */
1226                 new_settings.c_lflag &= ~ICANON;        /* unbuffered input */
1227                 new_settings.c_lflag &= ~(ECHO | ECHOCTL | ECHONL);     /* Turn off echoing */
1228         }
1229
1230         command[0] = 0;
1231
1232         setTermSettings(inputFd, (void *) &new_settings);
1233         handlers_sets |= SET_RESET_TERM;
1234
1235         /* Print out the command prompt */
1236         parse_prompt(prompt);
1237         /* Now initialize things */
1238         cmdedit_init();
1239
1240         while (1) {
1241
1242                 fflush(stdout);                 /* buffered out to fast */
1243
1244                 if (read(inputFd, &c, 1) < 1)
1245                         return;
1246
1247                 switch (c) {
1248                 case '\n':
1249                 case '\r':
1250                         /* Enter */
1251                         goto_new_line();
1252                         break_out = 1;
1253                         break;
1254                 case 1:
1255                         /* Control-a -- Beginning of line */
1256                         input_backward(cursor);
1257                         break;
1258                 case 2:
1259                         /* Control-b -- Move back one character */
1260                         input_backward(1);
1261                         break;
1262                 case 3:
1263                         /* Control-c -- stop gathering input */
1264
1265                         /* Link into lash to reset context to 0 on ^C and such */
1266                         shell_context = 0;
1267
1268                         /* Go to the next line */
1269                         goto_new_line();
1270                         command[0] = 0;
1271
1272                         return;
1273                 case 4:
1274                         /* Control-d -- Delete one character, or exit
1275                          * if the len=0 and no chars to delete */
1276                         if (len == 0) {
1277                                 printf("exit");
1278                                 clean_up_and_die(0);
1279                         } else {
1280                                 input_delete();
1281                         }
1282                         break;
1283                 case 5:
1284                         /* Control-e -- End of line */
1285                         input_end();
1286                         break;
1287                 case 6:
1288                         /* Control-f -- Move forward one character */
1289                         input_forward();
1290                         break;
1291                 case '\b':
1292                 case DEL:
1293                         /* Control-h and DEL */
1294                         input_backspace();
1295                         break;
1296                 case '\t':
1297 #ifdef BB_FEATURE_SH_TAB_COMPLETION
1298                         input_tab(&lastWasTab);
1299 #endif
1300                         break;
1301                 case 14:
1302                         /* Control-n -- Get next command in history */
1303                         if (hp && hp->n && hp->n->s) {
1304                                 get_next_history(&hp);
1305                                 goto rewrite_line;
1306                         } else {
1307                                 beep();
1308                         }
1309                         break;
1310                 case 16:
1311                         /* Control-p -- Get previous command from history */
1312                         if (hp && hp->p) {
1313                                 get_previous_history(&hp, hp->p);
1314                                 goto rewrite_line;
1315                         } else {
1316                                 beep();
1317                         }
1318                         break;
1319                 case 21:
1320                         /* Control-U -- Clear line before cursor */
1321                         if (cursor) {
1322                                 strcpy(command, command + cursor);
1323                                 redraw(cmdedit_y, len -= cursor);
1324                         }
1325                         break;
1326
1327                 case ESC:{
1328                         /* escape sequence follows */
1329                         if (read(inputFd, &c, 1) < 1)
1330                                 return;
1331                         /* different vt100 emulations */
1332                         if (c == '[' || c == 'O') {
1333                                 if (read(inputFd, &c, 1) < 1)
1334                                         return;
1335                         }
1336                         switch (c) {
1337 #ifdef BB_FEATURE_SH_TAB_COMPLETION
1338                         case '\t':                      /* Alt-Tab */
1339
1340                                 input_tab(&lastWasTab);
1341                                 break;
1342 #endif
1343                         case 'A':
1344                                 /* Up Arrow -- Get previous command from history */
1345                                 if (hp && hp->p) {
1346                                         get_previous_history(&hp, hp->p);
1347                                         goto rewrite_line;
1348                                 } else {
1349                                         beep();
1350                                 }
1351                                 break;
1352                         case 'B':
1353                                 /* Down Arrow -- Get next command in history */
1354                                 if (hp && hp->n && hp->n->s) {
1355                                         get_next_history(&hp);
1356                                         goto rewrite_line;
1357                                 } else {
1358                                         beep();
1359                                 }
1360                                 break;
1361
1362                                 /* Rewrite the line with the selected history item */
1363                           rewrite_line:
1364                                 /* change command */
1365                                 len = strlen(strcpy(command, hp->s));
1366                                 /* redraw and go to end line */
1367                                 redraw(cmdedit_y, 0);
1368                                 break;
1369                         case 'C':
1370                                 /* Right Arrow -- Move forward one character */
1371                                 input_forward();
1372                                 break;
1373                         case 'D':
1374                                 /* Left Arrow -- Move back one character */
1375                                 input_backward(1);
1376                                 break;
1377                         case '3':
1378                                 /* Delete */
1379                                 input_delete();
1380                                 break;
1381                         case '1':
1382                         case 'H':
1383                                 /* Home (Ctrl-A) */
1384                                 input_backward(cursor);
1385                                 break;
1386                         case '4':
1387                         case 'F':
1388                                 /* End (Ctrl-E) */
1389                                 input_end();
1390                                 break;
1391                         default:
1392                                 if (!(c >= '1' && c <= '9'))
1393                                         c = 0;
1394                                 beep();
1395                         }
1396                         if (c >= '1' && c <= '9')
1397                                 do
1398                                         if (read(inputFd, &c, 1) < 1)
1399                                                 return;
1400                                 while (c != '~');
1401                         break;
1402                 }
1403
1404                 default:                                /* If it's regular input, do the normal thing */
1405 #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT
1406                         /* Control-V -- Add non-printable symbol */
1407                         if (c == 22) {
1408                                 if (read(inputFd, &c, 1) < 1)
1409                                         return;
1410                                 if (c == 0) {
1411                                         beep();
1412                                         break;
1413                                 }
1414                         } else
1415 #endif
1416                         if (!isprint(c))        /* Skip non-printable characters */
1417                                 break;
1418
1419                         if (len >= (BUFSIZ - 2))        /* Need to leave space for enter */
1420                                 break;
1421
1422                         len++;
1423
1424                         if (cursor == (len - 1)) {      /* Append if at the end of the line */
1425                                 *(command + cursor) = c;
1426                                 *(command + cursor + 1) = 0;
1427                                 cmdedit_set_out_char(0);
1428                         } else {                        /* Insert otherwise */
1429                                 int sc = cursor;
1430
1431                                 memmove(command + sc + 1, command + sc, len - sc);
1432                                 *(command + sc) = c;
1433                                 sc++;
1434                                 /* rewrite from cursor */
1435                                 input_end();
1436                                 /* to prev x pos + 1 */
1437                                 input_backward(cursor - sc);
1438                         }
1439
1440                         break;
1441                 }
1442                 if (break_out)                  /* Enter is the command terminator, no more input. */
1443                         break;
1444
1445                 if (c != '\t')
1446                         lastWasTab = FALSE;
1447         }
1448
1449         setTermSettings(inputFd, (void *) &initial_settings);
1450         handlers_sets &= ~SET_RESET_TERM;
1451
1452         /* Handle command history log */
1453         if (len) {                                      /* no put empty line */
1454
1455                 struct history *h = his_end;
1456                 char *ss;
1457
1458                 ss = xstrdup(command);  /* duplicate */
1459
1460                 if (h == 0) {
1461                         /* No previous history -- this memory is never freed */
1462                         h = his_front = xmalloc(sizeof(struct history));
1463                         h->n = xmalloc(sizeof(struct history));
1464
1465                         h->p = NULL;
1466                         h->s = ss;
1467                         h->n->p = h;
1468                         h->n->n = NULL;
1469                         h->n->s = NULL;
1470                         his_end = h->n;
1471                         history_counter++;
1472                 } else {
1473                         /* Add a new history command -- this memory is never freed */
1474                         h->n = xmalloc(sizeof(struct history));
1475
1476                         h->n->p = h;
1477                         h->n->n = NULL;
1478                         h->n->s = NULL;
1479                         h->s = ss;
1480                         his_end = h->n;
1481
1482                         /* After max history, remove the oldest command */
1483                         if (history_counter >= MAX_HISTORY) {
1484
1485                                 struct history *p = his_front->n;
1486
1487                                 p->p = NULL;
1488                                 free(his_front->s);
1489                                 free(his_front);
1490                                 his_front = p;
1491                         } else {
1492                                 history_counter++;
1493                         }
1494                 }
1495 #if defined(BB_FEATURE_BASH_STYLE_PROMT)
1496                 num_ok_lines++;
1497 #endif
1498         }
1499         command[len++] = '\n';          /* set '\n' */
1500         command[len] = 0;
1501 #if defined(BB_FEATURE_CLEAN_UP) && defined(BB_FEATURE_SH_TAB_COMPLETION)
1502         input_tab(0);                           /* strong free */
1503 #endif
1504 #if defined(BB_FEATURE_BASH_STYLE_PROMT)
1505         free(cmdedit_prompt);
1506 #endif
1507         return;
1508 }
1509
1510
1511 /* Undo the effects of cmdedit_init(). */
1512 extern void cmdedit_terminate(void)
1513 {
1514         cmdedit_reset_term();
1515         if ((handlers_sets & SET_TERM_HANDLERS) != 0) {
1516                 signal(SIGKILL, SIG_DFL);
1517                 signal(SIGINT, SIG_DFL);
1518                 signal(SIGQUIT, SIG_DFL);
1519                 signal(SIGTERM, SIG_DFL);
1520                 signal(SIGWINCH, SIG_DFL);
1521                 handlers_sets &= ~SET_TERM_HANDLERS;
1522         }
1523 }
1524
1525 #endif                                                  /* BB_FEATURE_SH_COMMAND_EDITING */
1526
1527
1528 #ifdef TEST
1529
1530 unsigned int shell_context;
1531
1532 int main(int argc, char **argv)
1533 {
1534         char buff[BUFSIZ];
1535         char *prompt =
1536 #if defined(BB_FEATURE_BASH_STYLE_PROMT)
1537                 "\\[\\033[32;1m\\]\\u@\\[\\033[33;1m\\]\\h:\
1538 \\[\\033[34;1m\\]\\w\\[\\033[35;1m\\] \
1539 \\!\\[\\033[36;1m\\]\\$ \\[\\033[0m\\]";
1540 #else
1541                 "% ";
1542 #endif
1543
1544         shell_context = 1;
1545         do {
1546                 cmdedit_read_input(prompt, buff);
1547                 printf("*** cmdedit_read_input() returned line =%s=\n", buff);
1548         } while (shell_context);
1549         printf("*** cmdedit_read_input() detect ^C\n");
1550         return 0;
1551 }
1552
1553 #endif                                                  /* TEST */