source: branches/stable/mindi-busybox/libbb/lineedit.c @ 1770

Last change on this file since 1770 was 1770, checked in by Bruno Cornec, 13 years ago
  • Better output for mindi-busybox revision
  • Remove dummy file created on NFS - report from Arnaud Tiger <arnaud.tiger_at_hp.com>
  • strace useful for debug
  • fix new versions for pb (2.0.0 for mindi and 1.7.2 for mindi-busybox)
  • fix build process for mindi-busybox + options used in that version (dd for label-partitions-as-necessary)
  • fix typo in label-partitions-as-necessary which doesn't seem to work
  • Update to busybox 1.7.2
  • perl is now required at restore time to support uuid swap partitions (and will be used for many other thigs

in the future for sure)

  • next mindi version will be 2.0.0 due to all the changes made in it (udev may break working distros)
  • small optimization in mindi on keyboard handling (one single find instead of multiple)
  • better interaction for USB device when launching mindi manually
  • attempt to automatically guess block disk size for ramdisk
  • fix typos in bkphw
  • Fix the remaining problem with UUID support for swap partitions
  • Updates mondoarchive man page for USB support
  • Adds preliminary Hardware support to mindi (Proliant SSSTK)
  • Tries to add udev support also for rhel4
  • Fix UUID support which was still broken.
  • Be conservative in test for the start-nfs script
  • Update config file for mindi-busybox for 1.7.2 migration
  • Try to run around a busybox bug (1.2.2 pb on inexistant links)
  • Add build content for mindi-busybox in pb
  • Remove distributions content for mindi-busybox
  • Fix a warning on inexistant raidtab
  • Solve problem on tmpfs in restore init (Problem of inexistant symlink and busybox)
  • Create MONDO_CACHE and use it everywhere + creation at start
  • Really never try to eject a USB device
  • Fix a issue with &> usage (replaced with 1> and 2>)
  • Adds magic file to depllist in order to have file working + ldd which helps for debugging issues
  • tty modes correct to avoid sh error messages
  • Use ext3 normally and not ext2 instead
  • USB device should be corrected after reading (take 1st part)
  • Adds a mount_USB_here function derived from mount_CDROM_here
  • usb detection place before /dev detection in device name at restore time
  • Fix when restoring from USB: media is asked in interactive mode
  • Adds USB support for mondorestore
  • mount_cdrom => mount_media
  • elilo.efi is now searched throughout /boot/efi and not in a fixed place as there is no standard
  • untar-and-softlink => untar (+ interface change)
  • suppress useless softlinks creation/removal in boot process
  • avoids udevd messages on groups
  • Increase # of disks to 99 as in mindi at restore time (should be a conf file parameter)
  • skip existing big file creation
  • seems to work correctly for USB mindi boot
  • Adds group and tty link to udev conf
  • Always load usb-torage (even 2.6) to initiate USB bus discovery
  • Better printing of messages
  • Attempt to fix a bug in supporting OpenSusE 10.3 kernel for initramfs (mindi may now use multiple regex for kernel initrd detection)
  • Links were not correctly done as non relative for modules in mindi
  • exclusion of modules denied now works
  • Also create modules in their ordinary place, so that classical modprobe works + copy modules.dep
  • Fix bugs for DENY_MODS handling
  • Add device /dev/console for udev
  • ide-generic should now really be excluded
  • Fix a bug in major number for tty
  • If udev then adds modprobe/insmod to rootfs
  • tty0 is also cretaed with udev
  • ide-generic put rather in DENY_MODS
  • udevd remove from deplist s handled in mindi directly
  • better default for mindi when using --usb
  • Handles dynamically linked busybox (in case we want to use it soon ;-)
  • Adds fixed devices to create for udev
  • ide-generic should not be part of the initrd when using libata v2
  • support a dynamically linked udev (case on Ubuntu 7.10 and Mandriva 2008.0 so should be quite generic) This will give incitation to move to dyn. linked binaries in the initrd which will help for other tasks (ia6 4)
  • Improvement in udev support (do not use cl options not available in busybox)
  • Udev in mindi
    • auto creation of the right links at boot time with udev-links.conf(from Mandriva 2008.0)
    • rework startup of udev as current makes kernel crash (from Mandriva 2008.0)
    • add support for 64 bits udev
  • Try to render MyInsmod? silent at boot time
  • Adds udev support (mandatory for newest distributions to avoid remapping of devices in a different way as on the original system)
  • We also need vaft format support for USB boot
  • Adds libusual support (Ubuntu 7.10 needs it for USB)
  • Improve Ubuntu/Debian? keyboard detection and support
  • pbinit adapted to new pb (0.8.10). Filtering of docs done in it
  • Suppress some mondo warnings and errors on USB again
  • Tries to fix lack of files in deb mindi package
  • Verify should now work for USB devices
  • More log/mesages improvement for USB support
  • - Supress g_erase_tmpdir_and_scratchdir
  • Improve some log messages for USB support
  • Try to improve install in mindi to avoid issues with isolinux.cfg not installed vene if in the pkg :-(
  • Improve mindi-busybox build
  • In conformity with pb 0.8.9
  • Add support for Ubuntu 7.10 in build process
  • Add USB Key button to Menu UI (CD streamer removed)
  • Attempt to fix error messages on tmp/scratch files at the end by removing those dir at the latest possible.
  • Fix a bug linked to the size of the -E param which could be used (Arnaud Tiger/René? Ribaud).
  • Integrate ~/.pbrc content into mondorescue.pb (required project-builder >= 0.8.7)
  • Put mondorescue in conformity with new pb filtering rules
  • Add USB support at restore time (no test done yet). New start-usb script PB varibale added where useful
  • Unmounting USB device before removal of temporary scratchdir
  • Stil refining USB copy back to mondo (one command was not executed)
  • No need to have the image subdor in the csratchdir when USB.
  • umount the USB partition before attempting to use it
  • Remove useless copy from mindi to mondo at end of USB handling

(risky merge, we are raising the limits of 2 diverging branches. The status of stable is not completely sure as such. Will need lots of tests, but it's not yet done :-()
(merge -r1692:1769 $SVN_M/branches/2.2.5)

  • Property svn:eol-style set to native
File size: 41.3 KB
Line 
1/* vi: set sw=4 ts=4: */
2/*
3 * Termios command line History and Editing.
4 *
5 * Copyright (c) 1986-2003 may safely be consumed by a BSD or GPL license.
6 * Written by:   Vladimir Oleynik <dzo@simtreas.ru>
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    <andersen@codepoet.org> (Majorly adjusted for busybox)
13 *
14 * This code is 'as is' with no warranty.
15 */
16
17/*
18   Usage and known bugs:
19   Terminal key codes are not extensive, and more will probably
20   need to be added. This version was created on Debian GNU/Linux 2.x.
21   Delete, Backspace, Home, End, and the arrow keys were tested
22   to work in an Xterm and console. Ctrl-A also works as Home.
23   Ctrl-E also works as End.
24
25   Small bugs (simple effect):
26   - not true viewing if terminal size (x*y symbols) less
27     size (prompt + editor's line + 2 symbols)
28   - not true viewing if length prompt less terminal width
29 */
30
31#include "libbb.h"
32
33
34/* FIXME: obsolete CONFIG item? */
35#define ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT 0
36
37
38#ifdef TEST
39
40#define ENABLE_FEATURE_EDITING 0
41#define ENABLE_FEATURE_TAB_COMPLETION 0
42#define ENABLE_FEATURE_USERNAME_COMPLETION 0
43#define ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT 0
44#define ENABLE_FEATURE_CLEAN_UP 0
45
46#endif  /* TEST */
47
48
49/* Entire file (except TESTing part) sits inside this #if */
50#if ENABLE_FEATURE_EDITING
51
52#if ENABLE_LOCALE_SUPPORT
53#define Isprint(c) isprint(c)
54#else
55#define Isprint(c) ((c) >= ' ' && (c) != ((unsigned char)'\233'))
56#endif
57
58#define ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR \
59(ENABLE_FEATURE_USERNAME_COMPLETION || ENABLE_FEATURE_EDITING_FANCY_PROMPT)
60
61enum { MAX_LINELEN = CONFIG_FEATURE_EDITING_MAX_LEN };
62
63static line_input_t *state;
64
65static struct termios initial_settings, new_settings;
66
67static volatile unsigned cmdedit_termw = 80;        /* actual terminal width */
68
69static int cmdedit_x;           /* real x terminal position */
70static int cmdedit_y;           /* pseudoreal y terminal position */
71static int cmdedit_prmt_len;    /* length of prompt (without colors etc) */
72
73static unsigned cursor;
74static unsigned command_len;
75static char *command_ps;
76static const char *cmdedit_prompt;
77
78#if ENABLE_FEATURE_EDITING_FANCY_PROMPT
79static char *hostname_buf;
80static int num_ok_lines = 1;
81#endif
82
83#if ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR
84static const char null_str[] = "";
85static char *user_buf;
86static char *home_pwd_buf = (char*)null_str;
87#endif
88
89/* Put 'command_ps[cursor]', cursor++.
90 * Advance cursor on screen. If we reached right margin, scroll text up
91 * and remove terminal margin effect by printing 'next_char' */
92static void cmdedit_set_out_char(int next_char)
93{
94    int c = (unsigned char)command_ps[cursor];
95
96    if (c == '\0') {
97        /* erase character after end of input string */
98        c = ' ';
99    }
100#if ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT
101    /* Display non-printable characters in reverse */
102    if (!Isprint(c)) {
103        if (c >= 128)
104            c -= 128;
105        if (c < ' ')
106            c += '@';
107        if (c == 127)
108            c = '?';
109        printf("\033[7m%c\033[0m", c);
110    } else
111#endif
112    {
113        if (initial_settings.c_lflag & ECHO)
114            putchar(c);
115    }
116    if (++cmdedit_x >= cmdedit_termw) {
117        /* terminal is scrolled down */
118        cmdedit_y++;
119        cmdedit_x = 0;
120        /* destroy "(auto)margin" */
121        putchar(next_char);
122        putchar('\b');
123    }
124// Huh? What if command_ps[cursor] == '\0' (we are at the end already?)
125    cursor++;
126}
127
128/* Move to end of line (by printing all chars till the end) */
129static void input_end(void)
130{
131    while (cursor < command_len)
132        cmdedit_set_out_char(' ');
133}
134
135/* Go to the next line */
136static void goto_new_line(void)
137{
138    input_end();
139    if (cmdedit_x)
140        putchar('\n');
141}
142
143
144static void out1str(const char *s)
145{
146    if (s)
147        fputs(s, stdout);
148}
149
150static void beep(void)
151{
152    putchar('\007');
153}
154
155/* Move back one character */
156/* (optimized for slow terminals) */
157static void input_backward(unsigned num)
158{
159    int count_y;
160
161    if (num > cursor)
162        num = cursor;
163    if (!num)
164        return;
165    cursor -= num;
166
167    if (cmdedit_x >= num) {
168        cmdedit_x -= num;
169        if (num <= 4) {
170            printf("\b\b\b\b" + (4-num));
171            return;
172        }
173        printf("\033[%uD", num);
174        return;
175    }
176
177    /* Need to go one or more lines up */
178    num -= cmdedit_x;
179    count_y = 1 + (num / cmdedit_termw);
180    cmdedit_y -= count_y;
181    cmdedit_x = cmdedit_termw * count_y - num;
182    /* go to 1st column; go up; go to correct column */
183    printf("\r" "\033[%dA" "\033[%dC", count_y, cmdedit_x);
184}
185
186static void put_prompt(void)
187{
188    out1str(cmdedit_prompt);
189    cmdedit_x = cmdedit_prmt_len;
190    cursor = 0;
191// Huh? what if cmdedit_prmt_len >= width?
192    cmdedit_y = 0;                  /* new quasireal y */
193}
194
195/* draw prompt, editor line, and clear tail */
196static void redraw(int y, int back_cursor)
197{
198    if (y > 0)                              /* up to start y */
199        printf("\033[%dA", y);
200    putchar('\r');
201    put_prompt();
202    input_end();                            /* rewrite */
203    printf("\033[J");                       /* erase after cursor */
204    input_backward(back_cursor);
205}
206
207#if ENABLE_FEATURE_EDITING_VI
208#define DELBUFSIZ 128
209static char *delbuf;  /* a (malloced) place to store deleted characters */
210static char *delp;
211static char newdelflag;      /* whether delbuf should be reused yet */
212#endif
213
214/* Delete the char in front of the cursor, optionally saving it
215 * for later putback */
216static void input_delete(int save)
217{
218    int j = cursor;
219
220    if (j == command_len)
221        return;
222
223#if ENABLE_FEATURE_EDITING_VI
224    if (save) {
225        if (newdelflag) {
226            if (!delbuf)
227                delbuf = malloc(DELBUFSIZ);
228            /* safe if malloc fails */
229            delp = delbuf;
230            newdelflag = 0;
231        }
232        if (delbuf && (delp - delbuf < DELBUFSIZ))
233            *delp++ = command_ps[j];
234    }
235#endif
236
237    strcpy(command_ps + j, command_ps + j + 1);
238    command_len--;
239    input_end();                    /* rewrite new line */
240    cmdedit_set_out_char(' ');      /* erase char */
241    input_backward(cursor - j);     /* back to old pos cursor */
242}
243
244#if ENABLE_FEATURE_EDITING_VI
245static void put(void)
246{
247    int ocursor;
248    int j = delp - delbuf;
249
250    if (j == 0)
251        return;
252    ocursor = cursor;
253    /* open hole and then fill it */
254    memmove(command_ps + cursor + j, command_ps + cursor, command_len - cursor + 1);
255    strncpy(command_ps + cursor, delbuf, j);
256    command_len += j;
257    input_end();                    /* rewrite new line */
258    input_backward(cursor - ocursor - j + 1); /* at end of new text */
259}
260#endif
261
262/* Delete the char in back of the cursor */
263static void input_backspace(void)
264{
265    if (cursor > 0) {
266        input_backward(1);
267        input_delete(0);
268    }
269}
270
271/* Move forward one character */
272static void input_forward(void)
273{
274    if (cursor < command_len)
275        cmdedit_set_out_char(command_ps[cursor + 1]);
276}
277
278
279#if ENABLE_FEATURE_TAB_COMPLETION
280
281static char **matches;
282static unsigned num_matches;
283
284static void free_tab_completion_data(void)
285{
286    if (matches) {
287        while (num_matches)
288            free(matches[--num_matches]);
289        free(matches);
290        matches = NULL;
291    }
292}
293
294static void add_match(char *matched)
295{
296    int nm = num_matches;
297    int nm1 = nm + 1;
298
299    matches = xrealloc(matches, nm1 * sizeof(char *));
300    matches[nm] = matched;
301    num_matches++;
302}
303
304#if ENABLE_FEATURE_USERNAME_COMPLETION
305static void username_tab_completion(char *ud, char *with_shash_flg)
306{
307    struct passwd *entry;
308    int userlen;
309
310    ud++;                           /* ~user/... to user/... */
311    userlen = strlen(ud);
312
313    if (with_shash_flg) {           /* "~/..." or "~user/..." */
314        char *sav_ud = ud - 1;
315        char *home = NULL;
316        char *temp;
317
318        if (*ud == '/') {       /* "~/..."     */
319            home = home_pwd_buf;
320        } else {
321            /* "~user/..." */
322            temp = strchr(ud, '/');
323            *temp = 0;              /* ~user\0 */
324            entry = getpwnam(ud);
325            *temp = '/';            /* restore ~user/... */
326            ud = temp;
327            if (entry)
328                home = entry->pw_dir;
329        }
330        if (home) {
331            if ((userlen + strlen(home) + 1) < MAX_LINELEN) {
332                char temp2[MAX_LINELEN];     /* argument size */
333
334                /* /home/user/... */
335                sprintf(temp2, "%s%s", home, ud);
336                strcpy(sav_ud, temp2);
337            }
338        }
339    } else {
340        /* "~[^/]*" */
341        /* Using _r function to avoid pulling in static buffers */
342        char line_buff[256];
343        struct passwd pwd;
344        struct passwd *result;
345
346        setpwent();
347        while (!getpwent_r(&pwd, line_buff, sizeof(line_buff), &result)) {
348            /* Null usernames should result in all users as possible completions. */
349            if (/*!userlen || */ strncmp(ud, pwd.pw_name, userlen) == 0) {
350                add_match(xasprintf("~%s/", pwd.pw_name));
351            }
352        }
353        endpwent();
354    }
355}
356#endif  /* FEATURE_COMMAND_USERNAME_COMPLETION */
357
358enum {
359    FIND_EXE_ONLY = 0,
360    FIND_DIR_ONLY = 1,
361    FIND_FILE_ONLY = 2,
362};
363
364static int path_parse(char ***p, int flags)
365{
366    int npth;
367    const char *pth;
368    char *tmp;
369    char **res;
370
371    /* if not setenv PATH variable, to search cur dir "." */
372    if (flags != FIND_EXE_ONLY)
373        return 1;
374
375    if (state->flags & WITH_PATH_LOOKUP)
376        pth = state->path_lookup;
377    else
378        pth = getenv("PATH");
379    /* PATH=<empty> or PATH=:<empty> */
380    if (!pth || !pth[0] || LONE_CHAR(pth, ':'))
381        return 1;
382
383    tmp = (char*)pth;
384    npth = 1; /* path component count */
385    while (1) {
386        tmp = strchr(tmp, ':');
387        if (!tmp)
388            break;
389        if (*++tmp == '\0')
390            break;  /* :<empty> */
391        npth++;
392    }
393
394    res = xmalloc(npth * sizeof(char*));
395    res[0] = tmp = xstrdup(pth);
396    npth = 1;
397    while (1) {
398        tmp = strchr(tmp, ':');
399        if (!tmp)
400            break;
401        *tmp++ = '\0'; /* ':' -> '\0' */
402        if (*tmp == '\0')
403            break; /* :<empty> */
404        res[npth++] = tmp;
405    }
406    *p = res;
407    return npth;
408}
409
410static void exe_n_cwd_tab_completion(char *command, int type)
411{
412    DIR *dir;
413    struct dirent *next;
414    char dirbuf[MAX_LINELEN];
415    struct stat st;
416    char *path1[1];
417    char **paths = path1;
418    int npaths;
419    int i;
420    char *found;
421    char *pfind = strrchr(command, '/');
422
423    npaths = 1;
424    path1[0] = (char*)".";
425
426    if (pfind == NULL) {
427        /* no dir, if flags==EXE_ONLY - get paths, else "." */
428        npaths = path_parse(&paths, type);
429        pfind = command;
430    } else {
431        /* dirbuf = ".../.../.../" */
432        safe_strncpy(dirbuf, command, (pfind - command) + 2);
433#if ENABLE_FEATURE_USERNAME_COMPLETION
434        if (dirbuf[0] == '~')   /* ~/... or ~user/... */
435            username_tab_completion(dirbuf, dirbuf);
436#endif
437        paths[0] = dirbuf;
438        /* point to 'l' in "..../last_component" */
439        pfind++;
440    }
441
442    for (i = 0; i < npaths; i++) {
443        dir = opendir(paths[i]);
444        if (!dir)                       /* Don't print an error */
445            continue;
446
447        while ((next = readdir(dir)) != NULL) {
448            int len1;
449            const char *str_found = next->d_name;
450
451            /* matched? */
452            if (strncmp(str_found, pfind, strlen(pfind)))
453                continue;
454            /* not see .name without .match */
455            if (*str_found == '.' && *pfind == 0) {
456                if (NOT_LONE_CHAR(paths[i], '/') || str_found[1])
457                    continue;
458                str_found = ""; /* only "/" */
459            }
460            found = concat_path_file(paths[i], str_found);
461            /* hmm, remover in progress? */
462            if (stat(found, &st) < 0)
463                goto cont;
464            /* find with dirs? */
465            if (paths[i] != dirbuf)
466                strcpy(found, next->d_name);    /* only name */
467
468            len1 = strlen(found);
469            found = xrealloc(found, len1 + 2);
470            found[len1] = '\0';
471            found[len1+1] = '\0';
472
473            if (S_ISDIR(st.st_mode)) {
474                /* name is directory      */
475                if (found[len1-1] != '/') {
476                    found[len1] = '/';
477                }
478            } else {
479                /* not put found file if search only dirs for cd */
480                if (type == FIND_DIR_ONLY)
481                    goto cont;
482            }
483            /* Add it to the list */
484            add_match(found);
485            continue;
486 cont:
487            free(found);
488        }
489        closedir(dir);
490    }
491    if (paths != path1) {
492        free(paths[0]);                 /* allocated memory only in first member */
493        free(paths);
494    }
495}
496
497#define QUOT (UCHAR_MAX+1)
498
499#define collapse_pos(is, in) { \
500    memmove(int_buf+(is), int_buf+(in), (MAX_LINELEN+1-(is)-(in))*sizeof(int)); \
501    memmove(pos_buf+(is), pos_buf+(in), (MAX_LINELEN+1-(is)-(in))*sizeof(int)); }
502
503static int find_match(char *matchBuf, int *len_with_quotes)
504{
505    int i, j;
506    int command_mode;
507    int c, c2;
508    int int_buf[MAX_LINELEN + 1];
509    int pos_buf[MAX_LINELEN + 1];
510
511    /* set to integer dimension characters and own positions */
512    for (i = 0;; i++) {
513        int_buf[i] = (unsigned char)matchBuf[i];
514        if (int_buf[i] == 0) {
515            pos_buf[i] = -1;        /* indicator end line */
516            break;
517        }
518        pos_buf[i] = i;
519    }
520
521    /* mask \+symbol and convert '\t' to ' ' */
522    for (i = j = 0; matchBuf[i]; i++, j++)
523        if (matchBuf[i] == '\\') {
524            collapse_pos(j, j + 1);
525            int_buf[j] |= QUOT;
526            i++;
527#if ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT
528            if (matchBuf[i] == '\t')        /* algorithm equivalent */
529                int_buf[j] = ' ' | QUOT;
530#endif
531        }
532#if ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT
533        else if (matchBuf[i] == '\t')
534            int_buf[j] = ' ';
535#endif
536
537    /* mask "symbols" or 'symbols' */
538    c2 = 0;
539    for (i = 0; int_buf[i]; i++) {
540        c = int_buf[i];
541        if (c == '\'' || c == '"') {
542            if (c2 == 0)
543                c2 = c;
544            else {
545                if (c == c2)
546                    c2 = 0;
547                else
548                    int_buf[i] |= QUOT;
549            }
550        } else if (c2 != 0 && c != '$')
551            int_buf[i] |= QUOT;
552    }
553
554    /* skip commands with arguments if line has commands delimiters */
555    /* ';' ';;' '&' '|' '&&' '||' but `>&' `<&' `>|' */
556    for (i = 0; int_buf[i]; i++) {
557        c = int_buf[i];
558        c2 = int_buf[i + 1];
559        j = i ? int_buf[i - 1] : -1;
560        command_mode = 0;
561        if (c == ';' || c == '&' || c == '|') {
562            command_mode = 1 + (c == c2);
563            if (c == '&') {
564                if (j == '>' || j == '<')
565                    command_mode = 0;
566            } else if (c == '|' && j == '>')
567                command_mode = 0;
568        }
569        if (command_mode) {
570            collapse_pos(0, i + command_mode);
571            i = -1;                         /* hack incremet */
572        }
573    }
574    /* collapse `command...` */
575    for (i = 0; int_buf[i]; i++)
576        if (int_buf[i] == '`') {
577            for (j = i + 1; int_buf[j]; j++)
578                if (int_buf[j] == '`') {
579                    collapse_pos(i, j + 1);
580                    j = 0;
581                    break;
582                }
583            if (j) {
584                /* not found close ` - command mode, collapse all previous */
585                collapse_pos(0, i + 1);
586                break;
587            } else
588                i--;                    /* hack incremet */
589        }
590
591    /* collapse (command...(command...)...) or {command...{command...}...} */
592    c = 0;                                          /* "recursive" level */
593    c2 = 0;
594    for (i = 0; int_buf[i]; i++)
595        if (int_buf[i] == '(' || int_buf[i] == '{') {
596            if (int_buf[i] == '(')
597                c++;
598            else
599                c2++;
600            collapse_pos(0, i + 1);
601            i = -1;                         /* hack incremet */
602        }
603    for (i = 0; pos_buf[i] >= 0 && (c > 0 || c2 > 0); i++)
604        if ((int_buf[i] == ')' && c > 0) || (int_buf[i] == '}' && c2 > 0)) {
605            if (int_buf[i] == ')')
606                c--;
607            else
608                c2--;
609            collapse_pos(0, i + 1);
610            i = -1;                         /* hack incremet */
611        }
612
613    /* skip first not quote space */
614    for (i = 0; int_buf[i]; i++)
615        if (int_buf[i] != ' ')
616            break;
617    if (i)
618        collapse_pos(0, i);
619
620    /* set find mode for completion */
621    command_mode = FIND_EXE_ONLY;
622    for (i = 0; int_buf[i]; i++)
623        if (int_buf[i] == ' ' || int_buf[i] == '<' || int_buf[i] == '>') {
624            if (int_buf[i] == ' ' && command_mode == FIND_EXE_ONLY
625             && matchBuf[pos_buf[0]]=='c'
626             && matchBuf[pos_buf[1]]=='d'
627            ) {
628                command_mode = FIND_DIR_ONLY;
629            } else {
630                command_mode = FIND_FILE_ONLY;
631                break;
632            }
633        }
634    for (i = 0; int_buf[i]; i++)
635        /* "strlen" */;
636    /* find last word */
637    for (--i; i >= 0; i--) {
638        c = int_buf[i];
639        if (c == ' ' || c == '<' || c == '>' || c == '|' || c == '&') {
640            collapse_pos(0, i + 1);
641            break;
642        }
643    }
644    /* skip first not quoted '\'' or '"' */
645    for (i = 0; int_buf[i] == '\'' || int_buf[i] == '"'; i++)
646        /*skip*/;
647    /* collapse quote or unquote // or /~ */
648    while ((int_buf[i] & ~QUOT) == '/'
649     && ((int_buf[i+1] & ~QUOT) == '/' || (int_buf[i+1] & ~QUOT) == '~')
650    ) {
651        i++;
652    }
653
654    /* set only match and destroy quotes */
655    j = 0;
656    for (c = 0; pos_buf[i] >= 0; i++) {
657        matchBuf[c++] = matchBuf[pos_buf[i]];
658        j = pos_buf[i] + 1;
659    }
660    matchBuf[c] = 0;
661    /* old lenght matchBuf with quotes symbols */
662    *len_with_quotes = j ? j - pos_buf[0] : 0;
663
664    return command_mode;
665}
666
667/*
668 * display by column (original idea from ls applet,
669 * very optimized by me :)
670 */
671static void showfiles(void)
672{
673    int ncols, row;
674    int column_width = 0;
675    int nfiles = num_matches;
676    int nrows = nfiles;
677    int l;
678
679    /* find the longest file name-  use that as the column width */
680    for (row = 0; row < nrows; row++) {
681        l = strlen(matches[row]);
682        if (column_width < l)
683            column_width = l;
684    }
685    column_width += 2;              /* min space for columns */
686    ncols = cmdedit_termw / column_width;
687
688    if (ncols > 1) {
689        nrows /= ncols;
690        if (nfiles % ncols)
691            nrows++;        /* round up fractionals */
692    } else {
693        ncols = 1;
694    }
695    for (row = 0; row < nrows; row++) {
696        int n = row;
697        int nc;
698
699        for (nc = 1; nc < ncols && n+nrows < nfiles; n += nrows, nc++) {
700            printf("%s%-*s", matches[n],
701                (int)(column_width - strlen(matches[n])), "");
702        }
703        printf("%s\n", matches[n]);
704    }
705}
706
707static char *add_quote_for_spec_chars(char *found)
708{
709    int l = 0;
710    char *s = xmalloc((strlen(found) + 1) * 2);
711
712    while (*found) {
713        if (strchr(" `\"#$%^&*()=+{}[]:;\'|\\<>", *found))
714            s[l++] = '\\';
715        s[l++] = *found++;
716    }
717    s[l] = 0;
718    return s;
719}
720
721static int match_compare(const void *a, const void *b)
722{
723    return strcmp(*(char**)a, *(char**)b);
724}
725
726/* Do TAB completion */
727static void input_tab(int *lastWasTab)
728{
729    if (!(state->flags & TAB_COMPLETION))
730        return;
731
732    if (!*lastWasTab) {
733        char *tmp, *tmp1;
734        int len_found;
735        char matchBuf[MAX_LINELEN];
736        int find_type;
737        int recalc_pos;
738
739        *lastWasTab = TRUE;             /* flop trigger */
740
741        /* Make a local copy of the string -- up
742         * to the position of the cursor */
743        tmp = strncpy(matchBuf, command_ps, cursor);
744        tmp[cursor] = '\0';
745
746        find_type = find_match(matchBuf, &recalc_pos);
747
748        /* Free up any memory already allocated */
749        free_tab_completion_data();
750
751#if ENABLE_FEATURE_USERNAME_COMPLETION
752        /* If the word starts with `~' and there is no slash in the word,
753         * then try completing this word as a username. */
754        if (state->flags & USERNAME_COMPLETION)
755            if (matchBuf[0] == '~' && strchr(matchBuf, '/') == 0)
756                username_tab_completion(matchBuf, NULL);
757#endif
758        /* Try to match any executable in our path and everything
759         * in the current working directory */
760        if (!matches)
761            exe_n_cwd_tab_completion(matchBuf, find_type);
762        /* Sort, then remove any duplicates found */
763        if (matches) {
764            int i, n = 0;
765            qsort(matches, num_matches, sizeof(char*), match_compare);
766            for (i = 0; i < num_matches - 1; ++i) {
767                if (matches[i] && matches[i+1]) { /* paranoia */
768                    if (strcmp(matches[i], matches[i+1]) == 0) {
769                        free(matches[i]);
770                        matches[i] = NULL; /* paranoia */
771                    } else {
772                        matches[n++] = matches[i];
773                    }
774                }
775            }
776            matches[n] = matches[i];
777            num_matches = n + 1;
778        }
779        /* Did we find exactly one match? */
780        if (!matches || num_matches > 1) {
781            beep();
782            if (!matches)
783                return;         /* not found */
784            /* find minimal match */
785        // ash: yet another failure in trying to achieve "we don't die on OOM"
786            tmp1 = xstrdup(matches[0]);
787            for (tmp = tmp1; *tmp; tmp++)
788                for (len_found = 1; len_found < num_matches; len_found++)
789                    if (matches[len_found][(tmp - tmp1)] != *tmp) {
790                        *tmp = '\0';
791                        break;
792                    }
793            if (*tmp1 == '\0') {        /* have unique */
794                free(tmp1);
795                return;
796            }
797            tmp = add_quote_for_spec_chars(tmp1);
798            free(tmp1);
799        } else {                        /* one match */
800            tmp = add_quote_for_spec_chars(matches[0]);
801            /* for next completion current found */
802            *lastWasTab = FALSE;
803
804            len_found = strlen(tmp);
805            if (tmp[len_found-1] != '/') {
806                tmp[len_found] = ' ';
807                tmp[len_found+1] = '\0';
808            }
809        }
810        len_found = strlen(tmp);
811        /* have space to placed match? */
812        if ((len_found - strlen(matchBuf) + command_len) < MAX_LINELEN) {
813            /* before word for match   */
814            command_ps[cursor - recalc_pos] = 0;
815            /* save   tail line        */
816            strcpy(matchBuf, command_ps + cursor);
817            /* add    match            */
818            strcat(command_ps, tmp);
819            /* add    tail             */
820            strcat(command_ps, matchBuf);
821            /* back to begin word for match    */
822            input_backward(recalc_pos);
823            /* new pos                         */
824            recalc_pos = cursor + len_found;
825            /* new len                         */
826            command_len = strlen(command_ps);
827            /* write out the matched command   */
828            redraw(cmdedit_y, command_len - recalc_pos);
829        }
830        free(tmp);
831    } else {
832        /* Ok -- the last char was a TAB.  Since they
833         * just hit TAB again, print a list of all the
834         * available choices... */
835        if (matches && num_matches > 0) {
836            int sav_cursor = cursor;        /* change goto_new_line() */
837
838            /* Go to the next line */
839            goto_new_line();
840            showfiles();
841            redraw(0, command_len - sav_cursor);
842        }
843    }
844}
845
846#else
847#define input_tab(a) ((void)0)
848#endif  /* FEATURE_COMMAND_TAB_COMPLETION */
849
850
851#if MAX_HISTORY > 0
852
853/* state->flags is already checked to be nonzero */
854static void get_previous_history(void)
855{
856    if (command_ps[0] != '\0' || state->history[state->cur_history] == NULL) {
857        free(state->history[state->cur_history]);
858        state->history[state->cur_history] = xstrdup(command_ps);
859    }
860    state->cur_history--;
861}
862
863static int get_next_history(void)
864{
865    if (state->flags & DO_HISTORY) {
866        int ch = state->cur_history;
867        if (ch < state->cnt_history) {
868            get_previous_history(); /* save the current history line */
869            state->cur_history = ch + 1;
870            return state->cur_history;
871        }
872    }
873    beep();
874    return 0;
875}
876
877#if ENABLE_FEATURE_EDITING_SAVEHISTORY
878/* state->flags is already checked to be nonzero */
879static void load_history(const char *fromfile)
880{
881    FILE *fp;
882    int hi;
883
884    /* cleanup old */
885    for (hi = state->cnt_history; hi > 0;) {
886        hi--;
887        free(state->history[hi]);
888    }
889
890    fp = fopen(fromfile, "r");
891    if (fp) {
892        for (hi = 0; hi < MAX_HISTORY;) {
893            char *hl = xmalloc_getline(fp);
894            int l;
895
896            if (!hl)
897                break;
898            l = strlen(hl);
899            if (l >= MAX_LINELEN)
900                hl[MAX_LINELEN-1] = '\0';
901            if (l == 0 || hl[0] == ' ') {
902                free(hl);
903                continue;
904            }
905            state->history[hi++] = hl;
906        }
907        fclose(fp);
908    }
909    state->cur_history = state->cnt_history = hi;
910}
911
912/* state->flags is already checked to be nonzero */
913static void save_history(const char *tofile)
914{
915    FILE *fp;
916
917    fp = fopen(tofile, "w");
918    if (fp) {
919        int i;
920
921        for (i = 0; i < state->cnt_history; i++) {
922            fprintf(fp, "%s\n", state->history[i]);
923        }
924        fclose(fp);
925    }
926}
927#else
928#define load_history(a) ((void)0)
929#define save_history(a) ((void)0)
930#endif /* FEATURE_COMMAND_SAVEHISTORY */
931
932static void remember_in_history(const char *str)
933{
934    int i;
935
936    if (!(state->flags & DO_HISTORY))
937        return;
938
939    i = state->cnt_history;
940    free(state->history[MAX_HISTORY]);
941    state->history[MAX_HISTORY] = NULL;
942    /* After max history, remove the oldest command */
943    if (i >= MAX_HISTORY) {
944        free(state->history[0]);
945        for (i = 0; i < MAX_HISTORY-1; i++)
946            state->history[i] = state->history[i+1];
947    }
948// Maybe "if (!i || strcmp(history[i-1], command) != 0) ..."
949// (i.e. do not save dups?)
950    state->history[i++] = xstrdup(str);
951    state->cur_history = i;
952    state->cnt_history = i;
953#if ENABLE_FEATURE_EDITING_SAVEHISTORY
954    if ((state->flags & SAVE_HISTORY) && state->hist_file)
955        save_history(state->hist_file);
956#endif
957    USE_FEATURE_EDITING_FANCY_PROMPT(num_ok_lines++;)
958}
959
960#else /* MAX_HISTORY == 0 */
961#define remember_in_history(a) ((void)0)
962#endif /* MAX_HISTORY */
963
964
965/*
966 * This function is used to grab a character buffer
967 * from the input file descriptor and allows you to
968 * a string with full command editing (sort of like
969 * a mini readline).
970 *
971 * The following standard commands are not implemented:
972 * ESC-b -- Move back one word
973 * ESC-f -- Move forward one word
974 * ESC-d -- Delete back one word
975 * ESC-h -- Delete forward one word
976 * CTL-t -- Transpose two characters
977 *
978 * Minimalist vi-style command line editing available if configured.
979 * vi mode implemented 2005 by Paul Fox <pgf@foxharp.boston.ma.us>
980 */
981
982#if ENABLE_FEATURE_EDITING_VI
983static void
984vi_Word_motion(char *command, int eat)
985{
986    while (cursor < command_len && !isspace(command[cursor]))
987        input_forward();
988    if (eat) while (cursor < command_len && isspace(command[cursor]))
989        input_forward();
990}
991
992static void
993vi_word_motion(char *command, int eat)
994{
995    if (isalnum(command[cursor]) || command[cursor] == '_') {
996        while (cursor < command_len
997         && (isalnum(command[cursor+1]) || command[cursor+1] == '_'))
998            input_forward();
999    } else if (ispunct(command[cursor])) {
1000        while (cursor < command_len && ispunct(command[cursor+1]))
1001            input_forward();
1002    }
1003
1004    if (cursor < command_len)
1005        input_forward();
1006
1007    if (eat && cursor < command_len && isspace(command[cursor]))
1008        while (cursor < command_len && isspace(command[cursor]))
1009            input_forward();
1010}
1011
1012static void
1013vi_End_motion(char *command)
1014{
1015    input_forward();
1016    while (cursor < command_len && isspace(command[cursor]))
1017        input_forward();
1018    while (cursor < command_len-1 && !isspace(command[cursor+1]))
1019        input_forward();
1020}
1021
1022static void
1023vi_end_motion(char *command)
1024{
1025    if (cursor >= command_len-1)
1026        return;
1027    input_forward();
1028    while (cursor < command_len-1 && isspace(command[cursor]))
1029        input_forward();
1030    if (cursor >= command_len-1)
1031        return;
1032    if (isalnum(command[cursor]) || command[cursor] == '_') {
1033        while (cursor < command_len-1
1034         && (isalnum(command[cursor+1]) || command[cursor+1] == '_')
1035        ) {
1036            input_forward();
1037        }
1038    } else if (ispunct(command[cursor])) {
1039        while (cursor < command_len-1 && ispunct(command[cursor+1]))
1040            input_forward();
1041    }
1042}
1043
1044static void
1045vi_Back_motion(char *command)
1046{
1047    while (cursor > 0 && isspace(command[cursor-1]))
1048        input_backward(1);
1049    while (cursor > 0 && !isspace(command[cursor-1]))
1050        input_backward(1);
1051}
1052
1053static void
1054vi_back_motion(char *command)
1055{
1056    if (cursor <= 0)
1057        return;
1058    input_backward(1);
1059    while (cursor > 0 && isspace(command[cursor]))
1060        input_backward(1);
1061    if (cursor <= 0)
1062        return;
1063    if (isalnum(command[cursor]) || command[cursor] == '_') {
1064        while (cursor > 0
1065         && (isalnum(command[cursor-1]) || command[cursor-1] == '_')
1066        ) {
1067            input_backward(1);
1068        }
1069    } else if (ispunct(command[cursor])) {
1070        while (cursor > 0 && ispunct(command[cursor-1]))
1071            input_backward(1);
1072    }
1073}
1074#endif
1075
1076
1077/*
1078 * read_line_input and its helpers
1079 */
1080
1081#if !ENABLE_FEATURE_EDITING_FANCY_PROMPT
1082static void parse_prompt(const char *prmt_ptr)
1083{
1084    cmdedit_prompt = prmt_ptr;
1085    cmdedit_prmt_len = strlen(prmt_ptr);
1086    put_prompt();
1087}
1088#else
1089static void parse_prompt(const char *prmt_ptr)
1090{
1091    int prmt_len = 0;
1092    size_t cur_prmt_len = 0;
1093    char flg_not_length = '[';
1094    char *prmt_mem_ptr = xzalloc(1);
1095    char *pwd_buf = xrealloc_getcwd_or_warn(NULL);
1096    char buf2[PATH_MAX + 1];
1097    char buf[2];
1098    char c;
1099    char *pbuf;
1100
1101    cmdedit_prmt_len = 0;
1102
1103    if (!pwd_buf) {
1104        pwd_buf = (char *)bb_msg_unknown;
1105    }
1106
1107    while (*prmt_ptr) {
1108        pbuf = buf;
1109        pbuf[1] = 0;
1110        c = *prmt_ptr++;
1111        if (c == '\\') {
1112            const char *cp = prmt_ptr;
1113            int l;
1114
1115            c = bb_process_escape_sequence(&prmt_ptr);
1116            if (prmt_ptr == cp) {
1117                if (*cp == 0)
1118                    break;
1119                c = *prmt_ptr++;
1120                switch (c) {
1121#if ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR
1122                case 'u':
1123                    pbuf = user_buf ? user_buf : (char*)"";
1124                    break;
1125#endif
1126                case 'h':
1127                    pbuf = hostname_buf;
1128                    if (!pbuf) {
1129                        pbuf = xzalloc(256);
1130                        if (gethostname(pbuf, 255) < 0) {
1131                            strcpy(pbuf, "?");
1132                        } else {
1133                            char *s = strchr(pbuf, '.');
1134                            if (s)
1135                                *s = '\0';
1136                        }
1137                        hostname_buf = pbuf;
1138                    }
1139                    break;
1140                case '$':
1141                    c = (geteuid() == 0 ? '#' : '$');
1142                    break;
1143#if ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR
1144                case 'w':
1145                    pbuf = pwd_buf;
1146                    l = strlen(home_pwd_buf);
1147                    if (l != 0
1148                     && strncmp(home_pwd_buf, pbuf, l) == 0
1149                     && (pbuf[l]=='/' || pbuf[l]=='\0')
1150                     && strlen(pwd_buf+l)<PATH_MAX
1151                    ) {
1152                        pbuf = buf2;
1153                        *pbuf = '~';
1154                        strcpy(pbuf+1, pwd_buf+l);
1155                    }
1156                    break;
1157#endif
1158                case 'W':
1159                    pbuf = pwd_buf;
1160                    cp = strrchr(pbuf, '/');
1161                    if (cp != NULL && cp != pbuf)
1162                        pbuf += (cp-pbuf) + 1;
1163                    break;
1164                case '!':
1165                    pbuf = buf2;
1166                    snprintf(buf2, sizeof(buf2), "%d", num_ok_lines);
1167                    break;
1168                case 'e': case 'E':     /* \e \E = \033 */
1169                    c = '\033';
1170                    break;
1171                case 'x': case 'X':
1172                    for (l = 0; l < 3;) {
1173                        int h;
1174                        buf2[l++] = *prmt_ptr;
1175                        buf2[l] = 0;
1176                        h = strtol(buf2, &pbuf, 16);
1177                        if (h > UCHAR_MAX || (pbuf - buf2) < l) {
1178                            l--;
1179                            break;
1180                        }
1181                        prmt_ptr++;
1182                    }
1183                    buf2[l] = 0;
1184                    c = (char)strtol(buf2, NULL, 16);
1185                    if (c == 0)
1186                        c = '?';
1187                    pbuf = buf;
1188                    break;
1189                case '[': case ']':
1190                    if (c == flg_not_length) {
1191                        flg_not_length = flg_not_length == '[' ? ']' : '[';
1192                        continue;
1193                    }
1194                    break;
1195                }
1196            }
1197        }
1198        if (pbuf == buf)
1199            *pbuf = c;
1200        cur_prmt_len = strlen(pbuf);
1201        prmt_len += cur_prmt_len;
1202        if (flg_not_length != ']')
1203            cmdedit_prmt_len += cur_prmt_len;
1204        prmt_mem_ptr = strcat(xrealloc(prmt_mem_ptr, prmt_len+1), pbuf);
1205    }
1206    if (pwd_buf != (char *)bb_msg_unknown)
1207        free(pwd_buf);
1208    cmdedit_prompt = prmt_mem_ptr;
1209    put_prompt();
1210}
1211#endif
1212
1213#define setTermSettings(fd, argp) tcsetattr(fd, TCSANOW, argp)
1214#define getTermSettings(fd, argp) tcgetattr(fd, argp);
1215
1216static sighandler_t previous_SIGWINCH_handler;
1217
1218static void cmdedit_setwidth(unsigned w, int redraw_flg)
1219{
1220    cmdedit_termw = w;
1221    if (redraw_flg) {
1222        /* new y for current cursor */
1223        int new_y = (cursor + cmdedit_prmt_len) / w;
1224        /* redraw */
1225        redraw((new_y >= cmdedit_y ? new_y : cmdedit_y), command_len - cursor);
1226        fflush(stdout);
1227    }
1228}
1229
1230static void win_changed(int nsig)
1231{
1232    int width;
1233    get_terminal_width_height(0, &width, NULL);
1234    cmdedit_setwidth(width, nsig /* - just a yes/no flag */);
1235    if (nsig == SIGWINCH)
1236        signal(SIGWINCH, win_changed); /* rearm ourself */
1237}
1238
1239/*
1240 * The emacs and vi modes share much of the code in the big
1241 * command loop.  Commands entered when in vi's command mode (aka
1242 * "escape mode") get an extra bit added to distinguish them --
1243 * this keeps them from being self-inserted.  This clutters the
1244 * big switch a bit, but keeps all the code in one place.
1245 */
1246
1247#define vbit 0x100
1248
1249/* leave out the "vi-mode"-only case labels if vi editing isn't
1250 * configured. */
1251#define vi_case(caselabel) USE_FEATURE_EDITING(case caselabel)
1252
1253/* convert uppercase ascii to equivalent control char, for readability */
1254#undef CTRL
1255#define CTRL(a) ((a) & ~0x40)
1256
1257/* Returns:
1258 * -1 on read errors or EOF, or on bare Ctrl-D.
1259 * 0  on ctrl-C,
1260 * >0 length of input string, including terminating '\n'
1261 */
1262int read_line_input(const char* prompt, char* command, int maxsize, line_input_t *st)
1263{
1264    int lastWasTab = FALSE;
1265    unsigned int ic;
1266    unsigned char c;
1267    smallint break_out = 0;
1268#if ENABLE_FEATURE_EDITING_VI
1269    smallint vi_cmdmode = 0;
1270    smalluint prevc;
1271#endif
1272
1273// FIXME: audit & improve this
1274    if (maxsize > MAX_LINELEN)
1275        maxsize = MAX_LINELEN;
1276
1277    /* With null flags, no other fields are ever used */
1278    state = st ? st : (line_input_t*) &const_int_0;
1279#if ENABLE_FEATURE_EDITING_SAVEHISTORY
1280    if ((state->flags & SAVE_HISTORY) && state->hist_file)
1281        load_history(state->hist_file);
1282#endif
1283
1284    /* prepare before init handlers */
1285    cmdedit_y = 0;  /* quasireal y, not true if line > xt*yt */
1286    command_len = 0;
1287    command_ps = command;
1288    command[0] = '\0';
1289
1290    getTermSettings(0, (void *) &initial_settings);
1291    memcpy(&new_settings, &initial_settings, sizeof(new_settings));
1292    new_settings.c_lflag &= ~ICANON;        /* unbuffered input */
1293    /* Turn off echoing and CTRL-C, so we can trap it */
1294    new_settings.c_lflag &= ~(ECHO | ECHONL | ISIG);
1295    /* Hmm, in linux c_cc[] is not parsed if ICANON is off */
1296    new_settings.c_cc[VMIN] = 1;
1297    new_settings.c_cc[VTIME] = 0;
1298    /* Turn off CTRL-C, so we can trap it */
1299#ifndef _POSIX_VDISABLE
1300#define _POSIX_VDISABLE '\0'
1301#endif
1302    new_settings.c_cc[VINTR] = _POSIX_VDISABLE;
1303    setTermSettings(0, (void *) &new_settings);
1304
1305    /* Now initialize things */
1306    previous_SIGWINCH_handler = signal(SIGWINCH, win_changed);
1307    win_changed(0); /* do initial resizing */
1308#if ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR
1309    {
1310        struct passwd *entry;
1311
1312        entry = getpwuid(geteuid());
1313        if (entry) {
1314            /* If we enter read_line_input for the Nth time,
1315             * they may be already allocated! Need to free. */
1316            free(user_buf);
1317            if (home_pwd_buf != null_str)
1318                free(home_pwd_buf);
1319            user_buf = xstrdup(entry->pw_name);
1320            home_pwd_buf = xstrdup(entry->pw_dir);
1321            /* They are not freed on exit (too small to bother) */
1322        }
1323    }
1324#endif
1325    /* Print out the command prompt */
1326    parse_prompt(prompt);
1327
1328    while (1) {
1329        fflush(stdout);
1330
1331        if (safe_read(0, &c, 1) < 1) {
1332            /* if we can't read input then exit */
1333            goto prepare_to_die;
1334        }
1335
1336        ic = c;
1337
1338#if ENABLE_FEATURE_EDITING_VI
1339        newdelflag = 1;
1340        if (vi_cmdmode)
1341            ic |= vbit;
1342#endif
1343        switch (ic) {
1344        case '\n':
1345        case '\r':
1346        vi_case('\n'|vbit:)
1347        vi_case('\r'|vbit:)
1348            /* Enter */
1349            goto_new_line();
1350            break_out = 1;
1351            break;
1352#if ENABLE_FEATURE_EDITING_FANCY_KEYS
1353        case CTRL('A'):
1354        vi_case('0'|vbit:)
1355            /* Control-a -- Beginning of line */
1356            input_backward(cursor);
1357            break;
1358        case CTRL('B'):
1359        vi_case('h'|vbit:)
1360        vi_case('\b'|vbit:)
1361        vi_case('\x7f'|vbit:) /* DEL */
1362            /* Control-b -- Move back one character */
1363            input_backward(1);
1364            break;
1365#endif
1366        case CTRL('C'):
1367        vi_case(CTRL('C')|vbit:)
1368            /* Control-c -- stop gathering input */
1369            goto_new_line();
1370            command_len = 0;
1371            break_out = -1; /* "do not append '\n'" */
1372            break;
1373        case CTRL('D'):
1374            /* Control-d -- Delete one character, or exit
1375             * if the len=0 and no chars to delete */
1376            if (command_len == 0) {
1377                errno = 0;
1378 prepare_to_die:
1379                /* to control stopped jobs */
1380                break_out = command_len = -1;
1381                break;
1382            }
1383            input_delete(0);
1384            break;
1385
1386#if ENABLE_FEATURE_EDITING_FANCY_KEYS
1387        case CTRL('E'):
1388        vi_case('$'|vbit:)
1389            /* Control-e -- End of line */
1390            input_end();
1391            break;
1392        case CTRL('F'):
1393        vi_case('l'|vbit:)
1394        vi_case(' '|vbit:)
1395            /* Control-f -- Move forward one character */
1396            input_forward();
1397            break;
1398#endif
1399
1400        case '\b':
1401        case '\x7f': /* DEL */
1402            /* Control-h and DEL */
1403            input_backspace();
1404            break;
1405
1406        case '\t':
1407            input_tab(&lastWasTab);
1408            break;
1409
1410#if ENABLE_FEATURE_EDITING_FANCY_KEYS
1411        case CTRL('K'):
1412            /* Control-k -- clear to end of line */
1413            command[cursor] = 0;
1414            command_len = cursor;
1415            printf("\033[J");
1416            break;
1417        case CTRL('L'):
1418        vi_case(CTRL('L')|vbit:)
1419            /* Control-l -- clear screen */
1420            printf("\033[H");
1421            redraw(0, command_len - cursor);
1422            break;
1423#endif
1424
1425#if MAX_HISTORY > 0
1426        case CTRL('N'):
1427        vi_case(CTRL('N')|vbit:)
1428        vi_case('j'|vbit:)
1429            /* Control-n -- Get next command in history */
1430            if (get_next_history())
1431                goto rewrite_line;
1432            break;
1433        case CTRL('P'):
1434        vi_case(CTRL('P')|vbit:)
1435        vi_case('k'|vbit:)
1436            /* Control-p -- Get previous command from history */
1437            if ((state->flags & DO_HISTORY) && state->cur_history > 0) {
1438                get_previous_history();
1439                goto rewrite_line;
1440            }
1441            beep();
1442            break;
1443#endif
1444
1445#if ENABLE_FEATURE_EDITING_FANCY_KEYS
1446        case CTRL('U'):
1447        vi_case(CTRL('U')|vbit:)
1448            /* Control-U -- Clear line before cursor */
1449            if (cursor) {
1450                strcpy(command, command + cursor);
1451                command_len -= cursor;
1452                redraw(cmdedit_y, command_len);
1453            }
1454            break;
1455#endif
1456        case CTRL('W'):
1457        vi_case(CTRL('W')|vbit:)
1458            /* Control-W -- Remove the last word */
1459            while (cursor > 0 && isspace(command[cursor-1]))
1460                input_backspace();
1461            while (cursor > 0 && !isspace(command[cursor-1]))
1462                input_backspace();
1463            break;
1464
1465#if ENABLE_FEATURE_EDITING_VI
1466        case 'i'|vbit:
1467            vi_cmdmode = 0;
1468            break;
1469        case 'I'|vbit:
1470            input_backward(cursor);
1471            vi_cmdmode = 0;
1472            break;
1473        case 'a'|vbit:
1474            input_forward();
1475            vi_cmdmode = 0;
1476            break;
1477        case 'A'|vbit:
1478            input_end();
1479            vi_cmdmode = 0;
1480            break;
1481        case 'x'|vbit:
1482            input_delete(1);
1483            break;
1484        case 'X'|vbit:
1485            if (cursor > 0) {
1486                input_backward(1);
1487                input_delete(1);
1488            }
1489            break;
1490        case 'W'|vbit:
1491            vi_Word_motion(command, 1);
1492            break;
1493        case 'w'|vbit:
1494            vi_word_motion(command, 1);
1495            break;
1496        case 'E'|vbit:
1497            vi_End_motion(command);
1498            break;
1499        case 'e'|vbit:
1500            vi_end_motion(command);
1501            break;
1502        case 'B'|vbit:
1503            vi_Back_motion(command);
1504            break;
1505        case 'b'|vbit:
1506            vi_back_motion(command);
1507            break;
1508        case 'C'|vbit:
1509            vi_cmdmode = 0;
1510            /* fall through */
1511        case 'D'|vbit:
1512            goto clear_to_eol;
1513
1514        case 'c'|vbit:
1515            vi_cmdmode = 0;
1516            /* fall through */
1517        case 'd'|vbit: {
1518            int nc, sc;
1519            sc = cursor;
1520            prevc = ic;
1521            if (safe_read(0, &c, 1) < 1)
1522                goto prepare_to_die;
1523            if (c == (prevc & 0xff)) {
1524                /* "cc", "dd" */
1525                input_backward(cursor);
1526                goto clear_to_eol;
1527                break;
1528            }
1529            switch (c) {
1530            case 'w':
1531            case 'W':
1532            case 'e':
1533            case 'E':
1534                switch (c) {
1535                case 'w':   /* "dw", "cw" */
1536                    vi_word_motion(command, vi_cmdmode);
1537                    break;
1538                case 'W':   /* 'dW', 'cW' */
1539                    vi_Word_motion(command, vi_cmdmode);
1540                    break;
1541                case 'e':   /* 'de', 'ce' */
1542                    vi_end_motion(command);
1543                    input_forward();
1544                    break;
1545                case 'E':   /* 'dE', 'cE' */
1546                    vi_End_motion(command);
1547                    input_forward();
1548                    break;
1549                }
1550                nc = cursor;
1551                input_backward(cursor - sc);
1552                while (nc-- > cursor)
1553                    input_delete(1);
1554                break;
1555            case 'b':  /* "db", "cb" */
1556            case 'B':  /* implemented as B */
1557                if (c == 'b')
1558                    vi_back_motion(command);
1559                else
1560                    vi_Back_motion(command);
1561                while (sc-- > cursor)
1562                    input_delete(1);
1563                break;
1564            case ' ':  /* "d ", "c " */
1565                input_delete(1);
1566                break;
1567            case '$':  /* "d$", "c$" */
1568            clear_to_eol:
1569                while (cursor < command_len)
1570                    input_delete(1);
1571                break;
1572            }
1573            break;
1574        }
1575        case 'p'|vbit:
1576            input_forward();
1577            /* fallthrough */
1578        case 'P'|vbit:
1579            put();
1580            break;
1581        case 'r'|vbit:
1582            if (safe_read(0, &c, 1) < 1)
1583                goto prepare_to_die;
1584            if (c == 0)
1585                beep();
1586            else {
1587                *(command + cursor) = c;
1588                putchar(c);
1589                putchar('\b');
1590            }
1591            break;
1592#endif /* FEATURE_COMMAND_EDITING_VI */
1593
1594        case '\x1b': /* ESC */
1595
1596#if ENABLE_FEATURE_EDITING_VI
1597            if (state->flags & VI_MODE) {
1598                /* ESC: insert mode --> command mode */
1599                vi_cmdmode = 1;
1600                input_backward(1);
1601                break;
1602            }
1603#endif
1604            /* escape sequence follows */
1605            if (safe_read(0, &c, 1) < 1)
1606                goto prepare_to_die;
1607            /* different vt100 emulations */
1608            if (c == '[' || c == 'O') {
1609        vi_case('['|vbit:)
1610        vi_case('O'|vbit:)
1611                if (safe_read(0, &c, 1) < 1)
1612                    goto prepare_to_die;
1613            }
1614            if (c >= '1' && c <= '9') {
1615                unsigned char dummy;
1616
1617                if (safe_read(0, &dummy, 1) < 1)
1618                    goto prepare_to_die;
1619                if (dummy != '~')
1620                    c = '\0';
1621            }
1622
1623            switch (c) {
1624#if ENABLE_FEATURE_TAB_COMPLETION
1625            case '\t':                      /* Alt-Tab */
1626                input_tab(&lastWasTab);
1627                break;
1628#endif
1629#if MAX_HISTORY > 0
1630            case 'A':
1631                /* Up Arrow -- Get previous command from history */
1632                if ((state->flags & DO_HISTORY) && state->cur_history > 0) {
1633                    get_previous_history();
1634                    goto rewrite_line;
1635                }
1636                beep();
1637                break;
1638            case 'B':
1639                /* Down Arrow -- Get next command in history */
1640                if (!get_next_history())
1641                    break;
1642 rewrite_line:
1643                /* Rewrite the line with the selected history item */
1644                /* change command */
1645                command_len = strlen(strcpy(command, state->history[state->cur_history]));
1646                /* redraw and go to eol (bol, in vi */
1647                redraw(cmdedit_y, (state->flags & VI_MODE) ? 9999 : 0);
1648                break;
1649#endif
1650            case 'C':
1651                /* Right Arrow -- Move forward one character */
1652                input_forward();
1653                break;
1654            case 'D':
1655                /* Left Arrow -- Move back one character */
1656                input_backward(1);
1657                break;
1658            case '3':
1659                /* Delete */
1660                input_delete(0);
1661                break;
1662            case '1': // vt100? linux vt? or what?
1663            case '7': // vt100? linux vt? or what?
1664            case 'H': /* xterm's <Home> */
1665                input_backward(cursor);
1666                break;
1667            case '4': // vt100? linux vt? or what?
1668            case '8': // vt100? linux vt? or what?
1669            case 'F': /* xterm's <End> */
1670                input_end();
1671                break;
1672            default:
1673                c = '\0';
1674                beep();
1675            }
1676            break;
1677
1678        default:        /* If it's regular input, do the normal thing */
1679#if ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT
1680            /* Control-V -- Add non-printable symbol */
1681            if (c == CTRL('V')) {
1682                if (safe_read(0, &c, 1) < 1)
1683                    goto prepare_to_die;
1684                if (c == 0) {
1685                    beep();
1686                    break;
1687                }
1688            } else
1689#endif
1690
1691#if ENABLE_FEATURE_EDITING_VI
1692            if (vi_cmdmode)  /* Don't self-insert */
1693                break;
1694#endif
1695            if (!Isprint(c)) /* Skip non-printable characters */
1696                break;
1697
1698            if (command_len >= (maxsize - 2))        /* Need to leave space for enter */
1699                break;
1700
1701            command_len++;
1702            if (cursor == (command_len - 1)) {      /* Append if at the end of the line */
1703                command[cursor] = c;
1704                command[cursor+1] = '\0';
1705                cmdedit_set_out_char(' ');
1706            } else {                        /* Insert otherwise */
1707                int sc = cursor;
1708
1709                memmove(command + sc + 1, command + sc, command_len - sc);
1710                command[sc] = c;
1711                sc++;
1712                /* rewrite from cursor */
1713                input_end();
1714                /* to prev x pos + 1 */
1715                input_backward(cursor - sc);
1716            }
1717            break;
1718        }
1719        if (break_out)                  /* Enter is the command terminator, no more input. */
1720            break;
1721
1722        if (c != '\t')
1723            lastWasTab = FALSE;
1724    }
1725
1726    if (command_len > 0)
1727        remember_in_history(command);
1728
1729    if (break_out > 0) {
1730        command[command_len++] = '\n';
1731        command[command_len] = '\0';
1732    }
1733
1734#if ENABLE_FEATURE_CLEAN_UP && ENABLE_FEATURE_TAB_COMPLETION
1735    free_tab_completion_data();
1736#endif
1737
1738#if ENABLE_FEATURE_EDITING_FANCY_PROMPT
1739    free((char*)cmdedit_prompt);
1740#endif
1741    /* restore initial_settings */
1742    setTermSettings(STDIN_FILENO, (void *) &initial_settings);
1743    /* restore SIGWINCH handler */
1744    signal(SIGWINCH, previous_SIGWINCH_handler);
1745    fflush(stdout);
1746    return command_len;
1747}
1748
1749line_input_t *new_line_input_t(int flags)
1750{
1751    line_input_t *n = xzalloc(sizeof(*n));
1752    n->flags = flags;
1753    return n;
1754}
1755
1756#else
1757
1758#undef read_line_input
1759int read_line_input(const char* prompt, char* command, int maxsize)
1760{
1761    fputs(prompt, stdout);
1762    fflush(stdout);
1763    fgets(command, maxsize, stdin);
1764    return strlen(command);
1765}
1766
1767#endif  /* FEATURE_COMMAND_EDITING */
1768
1769
1770/*
1771 * Testing
1772 */
1773
1774#ifdef TEST
1775
1776#include <locale.h>
1777
1778const char *applet_name = "debug stuff usage";
1779
1780int main(int argc, char **argv)
1781{
1782    char buff[MAX_LINELEN];
1783    char *prompt =
1784#if ENABLE_FEATURE_EDITING_FANCY_PROMPT
1785        "\\[\\033[32;1m\\]\\u@\\[\\x1b[33;1m\\]\\h:"
1786        "\\[\\033[34;1m\\]\\w\\[\\033[35;1m\\] "
1787        "\\!\\[\\e[36;1m\\]\\$ \\[\\E[0m\\]";
1788#else
1789        "% ";
1790#endif
1791
1792#if ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT
1793    setlocale(LC_ALL, "");
1794#endif
1795    while (1) {
1796        int l;
1797        l = read_line_input(prompt, buff);
1798        if (l <= 0 || buff[l-1] != '\n')
1799            break;
1800        buff[l-1] = 0;
1801        printf("*** read_line_input() returned line =%s=\n", buff);
1802    }
1803    printf("*** read_line_input() detect ^D\n");
1804    return 0;
1805}
1806
1807#endif  /* TEST */
Note: See TracBrowser for help on using the repository browser.