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