1 /* $NetBSD: complete.c,v 1.7 2006/09/27 15:23:34 christos Exp $ */ 2 3 /*- 4 * Copyright (c) 1997-2000,2005 The NetBSD Foundation, Inc. 5 * All rights reserved. 6 * 7 * This code is derived from software contributed to The NetBSD Foundation 8 * by Luke Mewburn. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 3. All advertising materials mentioning features or use of this software 19 * must display the following acknowledgement: 20 * This product includes software developed by the NetBSD 21 * Foundation, Inc. and its contributors. 22 * 4. Neither the name of The NetBSD Foundation nor the names of its 23 * contributors may be used to endorse or promote products derived 24 * from this software without specific prior written permission. 25 * 26 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 27 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 28 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 29 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 30 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 31 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 32 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 33 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 34 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 35 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 36 * POSSIBILITY OF SUCH DAMAGE. 37 */ 38 39 /* Most of this is derived or copied from src/usr.bin/ftp/complete.c (1.41). */ 40 41 42 #ifdef USE_READLINE 43 44 #include <sys/cdefs.h> 45 #ifndef lint 46 __RCSID("$NetBSD: complete.c,v 1.7 2006/09/27 15:23:34 christos Exp $"); 47 #endif /* not lint */ 48 49 /* 50 * FTP user program - command and file completion routines 51 */ 52 53 #include <sys/stat.h> 54 55 #include <ctype.h> 56 #include <err.h> 57 #include <dirent.h> 58 #include <glob.h> 59 #include <stdio.h> 60 #include <stdlib.h> 61 #include <string.h> 62 #include <histedit.h> 63 #include <sys/param.h> 64 #include <stringlist.h> 65 66 #include "rcv.h" /* includes "glob.h" */ 67 #include "extern.h" 68 #include "complete.h" 69 70 /* 71 * Global variables 72 */ 73 static int doglob = 1; /* glob local file names */ 74 75 #define ttyout stdout 76 #define ttywidth screenwidth /* in "glob.h" */ 77 #define ttyheight screenheight /* in "glob.h" */ 78 79 80 /* This should find the first command that matches the name given or 81 * NULL if none. Use the routine from mail in lex.c. 82 */ 83 #define getcmd(w) lex(w) 84 85 86 /************************************************************************/ 87 /* from src/usr.bin/ftp/utils.h (1.135) */ 88 89 /* 90 * List words in stringlist, vertically arranged 91 */ 92 static void 93 list_vertical(StringList *sl) 94 { 95 int i, j; 96 int columns, lines; 97 char *p; 98 size_t w, width; 99 100 width = 0; 101 102 for (i = 0 ; i < sl->sl_cur ; i++) { 103 w = strlen(sl->sl_str[i]); 104 if (w > width) 105 width = w; 106 } 107 width = (width + 8) &~ 7; 108 109 columns = ttywidth / width; 110 if (columns == 0) 111 columns = 1; 112 lines = (sl->sl_cur + columns - 1) / columns; 113 for (i = 0; i < lines; i++) { 114 for (j = 0; j < columns; j++) { 115 p = sl->sl_str[j * lines + i]; 116 if (p) 117 fputs(p, ttyout); 118 if (j * lines + i + lines >= sl->sl_cur) { 119 putc('\n', ttyout); 120 break; 121 } 122 if (p) { 123 w = strlen(p); 124 while (w < width) { 125 w = (w + 8) &~ 7; 126 (void)putc('\t', ttyout); 127 } 128 } 129 } 130 } 131 } 132 133 /* 134 * Copy characters from src into dst, \ quoting characters that require it 135 */ 136 static void 137 ftpvis(char *dst, size_t dstlen, const char *src, size_t srclen) 138 { 139 int di, si; 140 141 for (di = si = 0; 142 src[si] != '\0' && di < dstlen && si < srclen; 143 di++, si++) { 144 switch (src[si]) { 145 case '\\': 146 case ' ': 147 case '\t': 148 case '\r': 149 case '\n': 150 case '"': 151 dst[di++] = '\\'; 152 if (di >= dstlen) 153 break; 154 /* FALLTHROUGH */ 155 default: 156 dst[di] = src[si]; 157 } 158 } 159 dst[di] = '\0'; 160 } 161 162 /* 163 * sl_init() with inbuilt error checking 164 */ 165 static StringList * 166 ftp_sl_init(void) 167 { 168 StringList *p; 169 170 p = sl_init(); 171 if (p == NULL) 172 err(1, "Unable to allocate memory for stringlist"); 173 return (p); 174 } 175 176 177 /* 178 * sl_add() with inbuilt error checking 179 */ 180 static void 181 ftp_sl_add(StringList *sl, char *i) 182 { 183 184 if (sl_add(sl, i) == -1) 185 err(1, "Unable to add `%s' to stringlist", i); 186 } 187 188 /* 189 * strdup() with inbuilt error checking 190 */ 191 static char * 192 ftp_strdup(const char *str) 193 { 194 char *s; 195 196 if (str == NULL) 197 errx(1, "ftp_strdup() called with NULL argument"); 198 s = strdup(str); 199 if (s == NULL) 200 err(1, "Unable to allocate memory for string copy"); 201 return (s); 202 } 203 204 /* 205 * Glob a local file name specification with the expectation of a single 206 * return value. Can't control multiple values being expanded from the 207 * expression, we return only the first. 208 * Returns NULL on error, or a pointer to a buffer containing the filename 209 * that's the caller's responsiblity to free(3) when finished with. 210 */ 211 static char * 212 globulize(const char *pattern) 213 { 214 glob_t gl; 215 int flags; 216 char *p; 217 218 if (!doglob) 219 return (ftp_strdup(pattern)); 220 221 flags = GLOB_BRACE|GLOB_NOCHECK|GLOB_TILDE; 222 memset(&gl, 0, sizeof(gl)); 223 if (glob(pattern, flags, NULL, &gl) || gl.gl_pathc == 0) { 224 warnx("%s: not found", pattern); 225 globfree(&gl); 226 return (NULL); 227 } 228 p = ftp_strdup(gl.gl_pathv[0]); 229 globfree(&gl); 230 return (p); 231 } 232 233 /* from src/usr.bin/ftp/utils.h (1.135) */ 234 /************************************************************************/ 235 236 static int 237 comparstr(const void *a, const void *b) 238 { 239 return (strcmp(*(const char * const *)a, *(const char * const *)b)); 240 } 241 242 /* 243 * Determine if complete is ambiguous. If unique, insert. 244 * If no choices, error. If unambiguous prefix, insert that. 245 * Otherwise, list choices. words is assumed to be filtered 246 * to only contain possible choices. 247 * Args: 248 * word word which started the match 249 * list list by default 250 * words stringlist containing possible matches 251 * Returns a result as per el_set(EL_ADDFN, ...) 252 */ 253 static unsigned char 254 complete_ambiguous(EditLine *el, char *word, int list, StringList *words) 255 { 256 char insertstr[MAXPATHLEN]; 257 char *lastmatch, *p; 258 int i, j; 259 size_t matchlen, wordlen; 260 261 wordlen = strlen(word); 262 if (words->sl_cur == 0) 263 return (CC_ERROR); /* no choices available */ 264 265 if (words->sl_cur == 1) { /* only once choice available */ 266 p = words->sl_str[0] + wordlen; 267 if (*p == '\0') /* at end of word? */ 268 return (CC_REFRESH); 269 ftpvis(insertstr, sizeof(insertstr), p, strlen(p)); 270 if (el_insertstr(el, insertstr) == -1) 271 return (CC_ERROR); 272 else 273 return (CC_REFRESH); 274 } 275 276 if (!list) { 277 matchlen = 0; 278 lastmatch = words->sl_str[0]; 279 matchlen = strlen(lastmatch); 280 for (i = 1 ; i < words->sl_cur ; i++) { 281 for (j = wordlen ; j < strlen(words->sl_str[i]); j++) 282 if (lastmatch[j] != words->sl_str[i][j]) 283 break; 284 if (j < matchlen) 285 matchlen = j; 286 } 287 if (matchlen > wordlen) { 288 ftpvis(insertstr, sizeof(insertstr), 289 lastmatch + wordlen, matchlen - wordlen); 290 if (el_insertstr(el, insertstr) == -1) 291 return (CC_ERROR); 292 else 293 return (CC_REFRESH_BEEP); 294 } 295 } 296 297 putc('\n', ttyout); 298 qsort(words->sl_str, words->sl_cur, sizeof(char *), comparstr); 299 list_vertical(words); 300 return (CC_REDISPLAY); 301 } 302 303 /* 304 * Complete a mail command. 305 */ 306 static unsigned char 307 complete_command(EditLine *el, char *word, int list) 308 { 309 const struct cmd *c; 310 StringList *words; 311 size_t wordlen; 312 unsigned char rv; 313 314 words = ftp_sl_init(); 315 wordlen = strlen(word); 316 317 for (c = cmdtab; c->c_name != NULL; c++) { 318 if (wordlen > strlen(c->c_name)) 319 continue; 320 if (strncmp(word, c->c_name, wordlen) == 0) 321 ftp_sl_add(words, __UNCONST(c->c_name)); 322 } 323 324 rv = complete_ambiguous(el, word, list, words); 325 if (rv == CC_REFRESH) { 326 if (el_insertstr(el, " ") == -1) 327 rv = CC_ERROR; 328 } 329 sl_free(words, 0); 330 return (rv); 331 } 332 333 /* 334 * Complete a local filename. 335 */ 336 static unsigned char 337 complete_filename(EditLine *el, char *word, int list) 338 { 339 StringList *words; 340 char dir[MAXPATHLEN]; 341 char *fname; 342 DIR *dd; 343 struct dirent *dp; 344 unsigned char rv; 345 size_t len; 346 347 if ((fname = strrchr(word, '/')) == NULL) { 348 dir[0] = '.'; 349 dir[1] = '\0'; 350 fname = word; 351 } else { 352 if (fname == word) { 353 dir[0] = '/'; 354 dir[1] = '\0'; 355 } else 356 (void)strlcpy(dir, word, fname - word + 1); 357 fname++; 358 } 359 if (dir[0] == '~') { 360 char *p; 361 362 if ((p = globulize(dir)) == NULL) 363 return (CC_ERROR); 364 (void)strlcpy(dir, p, sizeof(dir)); 365 free(p); 366 } 367 368 if ((dd = opendir(dir)) == NULL) 369 return (CC_ERROR); 370 371 words = ftp_sl_init(); 372 len = strlen(fname); 373 374 for (dp = readdir(dd); dp != NULL; dp = readdir(dd)) { 375 if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) 376 continue; 377 378 #if defined(DIRENT_MISSING_D_NAMLEN) 379 if (len > strlen(dp->d_name)) 380 continue; 381 #else 382 if (len > dp->d_namlen) 383 continue; 384 #endif 385 if (strncmp(fname, dp->d_name, len) == 0) { 386 char *tcp; 387 388 tcp = ftp_strdup(dp->d_name); 389 ftp_sl_add(words, tcp); 390 } 391 } 392 closedir(dd); 393 394 rv = complete_ambiguous(el, fname, list, words); 395 if (rv == CC_REFRESH) { 396 struct stat sb; 397 char path[MAXPATHLEN]; 398 399 (void)strlcpy(path, dir, sizeof(path)); 400 (void)strlcat(path, "/", sizeof(path)); 401 (void)strlcat(path, words->sl_str[0], sizeof(path)); 402 403 if (stat(path, &sb) >= 0) { 404 char suffix[2] = " "; 405 406 if (S_ISDIR(sb.st_mode)) 407 suffix[0] = '/'; 408 if (el_insertstr(el, suffix) == -1) 409 rv = CC_ERROR; 410 } 411 } 412 sl_free(words, 1); 413 return (rv); 414 } 415 416 static int 417 find_execs(char *word, char *path, StringList *list) 418 { 419 char *sep; 420 char *dir=path; 421 DIR *dd; 422 struct dirent *dp; 423 int len = strlen(word); 424 uid_t uid = getuid(); 425 gid_t gid = getgid(); 426 427 for (sep=dir ; sep ; dir=sep+1) { 428 if ((sep=strchr(dir, ':')) != NULL) { 429 *sep=0; 430 } 431 432 if ((dd = opendir(dir)) == NULL) { 433 perror("dir"); 434 return -1; 435 } 436 437 for (dp = readdir(dd); dp != NULL; dp = readdir(dd)) { 438 439 if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) 440 continue; 441 442 #if defined(DIRENT_MISSING_D_NAMLEN) 443 if (len > strlen(dp->d_name)) 444 continue; 445 #else 446 if (len > dp->d_namlen) 447 continue; 448 #endif 449 450 if (strncmp(word, dp->d_name, len) == 0) { 451 struct stat sb; 452 char pathname[ MAXPATHLEN ]; 453 unsigned mask; 454 455 snprintf(pathname, sizeof(pathname), "%s/%s", dir, dp->d_name); 456 if (stat(pathname, &sb) != 0) { 457 perror(pathname); 458 continue; 459 } 460 461 mask = 0001; 462 if (sb.st_uid == uid) mask |= 0100; 463 if (sb.st_gid == gid) mask |= 0010; 464 465 if ((sb.st_mode & mask) != 0) { 466 char *tcp; 467 tcp = strdup(dp->d_name); 468 sl_add(list, tcp); 469 } 470 } 471 472 } 473 474 closedir(dd); 475 } 476 477 return 0; 478 } 479 480 481 /* 482 * Complete a local executable 483 */ 484 static unsigned char 485 complete_executable(EditLine *el, char *word, int list) 486 { 487 StringList *words; 488 char dir[ MAXPATHLEN ]; 489 char *fname; 490 unsigned char rv; 491 int error; 492 493 if ((fname = strrchr(word, '/')) == NULL) { 494 dir[0] = '\0'; /* walk the path */ 495 fname = word; 496 } else { 497 if (fname == word) { 498 dir[0] = '/'; 499 dir[1] = '\0'; 500 } else { 501 (void)strncpy(dir, word, fname - word); 502 dir[fname - word] = '\0'; 503 } 504 fname++; 505 } 506 507 words = sl_init(); 508 509 if (*dir == '\0') { /* walk path */ 510 char *env; 511 char *path; 512 int len; 513 env = getenv("PATH"); 514 len = strlen(env); 515 path = salloc(len + 1); 516 strcpy(path, env); 517 error = find_execs(word, path, words); 518 } 519 else { /* check specified dir only */ 520 error = find_execs(word, dir, words); 521 } 522 if (error != 0) 523 return CC_ERROR; 524 525 rv = complete_ambiguous(el, fname, list, words); 526 527 if (rv == CC_REFRESH) 528 if (el_insertstr(el, " ") == -1) 529 rv = CC_ERROR; 530 531 sl_free(words, 1); 532 533 return (rv); 534 } 535 536 537 static unsigned 538 char complete_set(EditLine *el, char *word, int list) 539 { 540 struct var *vp; 541 char **ap; 542 char **p; 543 int h; 544 int s; 545 int len = strlen(word); 546 StringList *words; 547 unsigned char rv; 548 549 words = sl_init(); 550 551 /* allocate space for variables table */ 552 for (h = 0, s = 1; h < HSHSIZE; h++) 553 for (vp = variables[h]; vp != NULL; vp = vp->v_link) 554 s++; 555 ap = (char **) salloc(s * sizeof *ap); 556 557 /* save pointers */ 558 for (h = 0, p = ap; h < HSHSIZE; h++) 559 for (vp = variables[h]; vp != NULL; vp = vp->v_link) 560 *p++ = vp->v_name; 561 *p = NULL; 562 563 /* sort pointers */ 564 sort(ap); 565 566 /* complete on list */ 567 for (p = ap; *p != NULL; p++) { 568 if (len == 0 || strncmp(*p, word, len) == 0) { 569 char *tcp; 570 tcp = strdup(*p); 571 sl_add(words, tcp); 572 } 573 } 574 575 rv = complete_ambiguous(el, word, list, words); 576 577 sl_free(words, 1); 578 579 return(rv); 580 } 581 582 583 584 585 586 static unsigned char 587 complete_alias(EditLine *el, char *word, int list) 588 { 589 struct grouphead *gh; 590 char **ap; 591 char **p; 592 int h; 593 int s; 594 int len = strlen(word); 595 StringList *words; 596 unsigned char rv; 597 598 words = sl_init(); 599 600 /* allocate space for alias table */ 601 for (h = 0, s = 1; h < HSHSIZE; h++) 602 for (gh = groups[h]; gh != NULL; gh = gh->g_link) 603 s++; 604 ap = (char **) salloc(s * sizeof *ap); 605 606 /* save pointers */ 607 for (h = 0, p = ap; h < HSHSIZE; h++) 608 for (gh = groups[h]; gh != NULL; gh = gh->g_link) 609 *p++ = gh->g_name; 610 *p = NULL; 611 612 /* sort pointers */ 613 sort(ap); 614 615 /* complete on list */ 616 for (p = ap; *p != NULL; p++) { 617 if (len == 0 || strncmp(*p, word, len) == 0) { 618 char *tcp; 619 tcp = strdup(*p); 620 sl_add(words, tcp); 621 } 622 } 623 624 rv = complete_ambiguous(el, word, list, words); 625 if (rv == CC_REFRESH) 626 if (el_insertstr(el, " ") == -1) 627 rv = CC_ERROR; 628 629 sl_free(words, 1); 630 631 return(rv); 632 } 633 634 635 636 static char *stringbase; /* current scan point in line buffer */ 637 static char *argbase; /* current storage point in arg buffer */ 638 static StringList *marg_sl; /* stringlist containing margv */ 639 static int margc; /* count of arguments on input line */ 640 #define margv (marg_sl->sl_str) /* args parsed from input line */ 641 642 static char *cursor_pos; 643 static int cursor_argc; 644 static int cursor_argo; 645 646 static void 647 init_complete(void) 648 { 649 marg_sl = sl_init(); 650 return; 651 } 652 653 654 655 /************************************************************************/ 656 /* from /usr/src/usr.bin/ftp/main.c(1.101) */ 657 658 static int slrflag; 659 static char *altarg; /* argv[1] with no shell-like preprocessing */ 660 661 #ifdef NO_EDITCOMPLETE 662 #define INC_CHKCURSOR(x) (x)++ 663 #else /* !NO_EDITCOMPLETE */ 664 #define INC_CHKCURSOR(x) \ 665 do { \ 666 (x)++ ; \ 667 if (x == cursor_pos) { \ 668 cursor_argc = margc; \ 669 cursor_argo = ap - argbase; \ 670 cursor_pos = NULL; \ 671 } \ 672 } while(0) 673 #endif /* !NO_EDITCOMPLETE */ 674 675 /* 676 * Parse string into argbuf; 677 * implemented with FSM to 678 * handle quoting and strings 679 */ 680 static char * 681 slurpstring(void) 682 { 683 int got_one = 0; 684 char *sb = stringbase; 685 char *ap = argbase; 686 char *tmp = argbase; /* will return this if token found */ 687 688 if (*sb == '!' || *sb == '$') { /* recognize ! as a token for shell */ 689 switch (slrflag) { /* and $ as token for macro invoke */ 690 case 0: 691 slrflag++; 692 INC_CHKCURSOR(stringbase); 693 return __UNCONST((*sb == '!') ? "!" : "$"); 694 /* NOTREACHED */ 695 case 1: 696 slrflag++; 697 altarg = stringbase; 698 break; 699 default: 700 break; 701 } 702 } 703 704 S0: 705 switch (*sb) { 706 707 case '\0': 708 goto OUT; 709 710 case ' ': 711 case '\t': 712 INC_CHKCURSOR(sb); 713 goto S0; 714 715 default: 716 switch (slrflag) { 717 case 0: 718 slrflag++; 719 break; 720 case 1: 721 slrflag++; 722 altarg = sb; 723 break; 724 default: 725 break; 726 } 727 goto S1; 728 } 729 730 S1: 731 switch (*sb) { 732 733 case ' ': 734 case '\t': 735 case '\0': 736 goto OUT; /* end of token */ 737 738 case '\\': 739 INC_CHKCURSOR(sb); 740 goto S2; /* slurp next character */ 741 742 case '"': 743 INC_CHKCURSOR(sb); 744 goto S3; /* slurp quoted string */ 745 746 default: 747 /* the first arg (command) is special - see execute() in lex.c */ 748 if (margc == 0 && index(" \t0123456789$^.:/-+*'\"", *sb)) 749 goto OUT; 750 751 *ap = *sb; /* add character to token */ 752 ap++; 753 INC_CHKCURSOR(sb); 754 got_one = 1; 755 goto S1; 756 } 757 758 S2: 759 switch (*sb) { 760 761 case '\0': 762 goto OUT; 763 764 default: 765 *ap = *sb; 766 ap++; 767 INC_CHKCURSOR(sb); 768 got_one = 1; 769 goto S1; 770 } 771 772 S3: 773 switch (*sb) { 774 775 case '\0': 776 goto OUT; 777 778 case '"': 779 INC_CHKCURSOR(sb); 780 goto S1; 781 782 default: 783 *ap = *sb; 784 ap++; 785 INC_CHKCURSOR(sb); 786 got_one = 1; 787 goto S3; 788 } 789 790 OUT: 791 if (got_one) 792 *ap++ = '\0'; 793 argbase = ap; /* update storage pointer */ 794 stringbase = sb; /* update scan pointer */ 795 if (got_one) { 796 return (tmp); 797 } 798 switch (slrflag) { 799 case 0: 800 slrflag++; 801 break; 802 case 1: 803 slrflag++; 804 altarg = NULL; 805 break; 806 default: 807 break; 808 } 809 return (NULL); 810 } 811 812 813 /* 814 * Slice a string up into argc/argv. 815 */ 816 static void 817 makeargv(char *line) 818 { 819 static char argbuf[ LINESIZE ]; /* argument storage buffer */ 820 char *argp; 821 822 stringbase = line; /* scan from first of buffer */ 823 argbase = argbuf; /* store from first of buffer */ 824 slrflag = 0; 825 marg_sl->sl_cur = 0; /* reset to start of marg_sl */ 826 for (margc = 0; ; margc++) { 827 argp = slurpstring(); 828 ftp_sl_add(marg_sl, argp); 829 if (argp == NULL) 830 break; 831 } 832 #ifndef NO_EDITCOMPLETE 833 if (cursor_pos == line) { 834 cursor_argc = 0; 835 cursor_argo = 0; 836 } else if (cursor_pos != NULL) { 837 cursor_argc = margc; 838 cursor_argo = strlen(margv[margc-1]); 839 } 840 #endif /* !NO_EDITCOMPLETE */ 841 } 842 843 /* from /usr/src/usr.bin/ftp/main.c(1.101) */ 844 /************************************************************************/ 845 846 847 /* 848 * Generic complete routine 849 */ 850 static unsigned char 851 complete(EditLine *el, int ch) 852 { 853 static char line[LINESIZE]; /* input line buffer */ 854 static char word[LINESIZE]; 855 static int lastc_argc, lastc_argo; 856 857 const struct cmd *c; 858 const LineInfo *lf; 859 int celems, dolist, cmpltype; 860 size_t len; 861 862 lf = el_line(el); 863 len = lf->lastchar - lf->buffer; 864 #if 1 865 if (ch == 04) { /* CTRL-D is special */ 866 if (len == 0) 867 return (CC_EOF); 868 if (lf->lastchar != lf->cursor) { 869 el_push(el, __UNCONST("")); /* delete current char without using ^D */ 870 return (CC_NORM); 871 } 872 } 873 #endif 874 if (len >= sizeof(line)) 875 return (CC_ERROR); 876 (void)strlcpy(line, lf->buffer, len + 1); 877 cursor_pos = line + (lf->cursor - lf->buffer); 878 lastc_argc = cursor_argc; /* remember last cursor pos */ 879 lastc_argo = cursor_argo; 880 makeargv(line); /* build argc/argv of current line */ 881 882 if (cursor_argo >= sizeof(word)) 883 return (CC_ERROR); 884 885 dolist = 0; 886 /* if cursor and word are the same, list alternatives */ 887 if (lastc_argc == cursor_argc && lastc_argo == cursor_argo 888 && strncmp(word, margv[cursor_argc] ? margv[cursor_argc] : "", 889 cursor_argo) == 0) 890 dolist = 1; 891 else if (cursor_argc < margc) 892 (void)strlcpy(word, margv[cursor_argc], cursor_argo + 1); 893 word[cursor_argo] = '\0'; 894 895 if (cursor_argc == 0) 896 return (complete_command(el, word, dolist)); 897 898 c = getcmd(margv[0]); 899 if (c == NULL) 900 return (CC_ERROR); 901 celems = strlen(c->c_complete); 902 903 /* check for 'continuation' completes (which are uppercase) */ 904 if ((cursor_argc > celems) && (celems > 0) 905 && isupper((unsigned char) c->c_complete[celems-1])) 906 cursor_argc = celems; 907 908 if (cursor_argc > celems) 909 return (CC_ERROR); 910 911 cmpltype = c->c_complete[cursor_argc - 1]; 912 switch (cmpltype) { 913 case 'a': /* alias complete */ 914 case 'A': 915 return (complete_alias(el, word, dolist)); 916 917 case 'c': /* command complete */ 918 case 'C': 919 return (complete_command(el, word, dolist)); 920 921 case 'f': /* filename complete */ 922 case 'F': 923 return (complete_filename(el, word, dolist)); 924 925 case 'n': /* no complete */ 926 case 'N': /* no complete */ 927 return (CC_ERROR); 928 929 case 's': 930 case 'S': 931 return (complete_set(el, word, dolist)); 932 933 case 'x': /* executable complete */ 934 case 'X': 935 return (complete_executable(el, word, dolist)); 936 937 default: 938 errx(1, "unknown complete type `%c'", cmpltype); 939 return (CC_ERROR); 940 } 941 /* NOTREACHED */ 942 } 943 944 945 /*************************************************************************/ 946 /* Most of this was originally taken directly from the readline manpage. */ 947 948 static struct { 949 EditLine *el; /* editline(3) with completion and history */ 950 EditLine *elo; /* editline(3) editline only, no completion */ 951 History *hist; /* editline(3) history structure */ 952 const char *prompt; /* prompt */ 953 } rl_global = { 954 .el = NULL, 955 .hist = NULL, 956 .prompt = NULL 957 }; 958 959 char * 960 rl_gets(const char *prompt) 961 { 962 int cnt; 963 const char *buf; 964 HistEvent ev; 965 static char line[LINE_MAX]; 966 967 rl_global.prompt = prompt; 968 buf = el_gets(rl_global.el, &cnt); 969 970 if (buf == NULL || cnt <= 0) 971 return NULL; 972 973 /* enter the line into history */ 974 if (history(rl_global.hist, &ev, H_ENTER, buf) == 0) 975 printf("Failed history entry: %s", buf); 976 #ifdef DEBUG 977 else 978 printf("history entry: %s\n", ev.str); 979 #endif 980 981 cnt--; /* trash the trailing LF */ 982 cnt = MIN(sizeof(line) - 1, cnt); 983 (void)memcpy(line, buf, cnt); 984 line[cnt] = '\0'; 985 986 return line; 987 } 988 989 990 /* 991 * Edit a line containing string, with no history or completion. 992 */ 993 char * 994 rl_getline(const char *prompt, char *string) 995 { 996 static char line[LINE_MAX]; 997 const char *buf; 998 int cnt; 999 1000 rl_global.prompt = prompt; 1001 1002 if (string) 1003 el_push(rl_global.elo, string); 1004 1005 buf = el_gets(rl_global.elo, &cnt); 1006 if (buf == NULL || cnt <= 0) { 1007 if (cnt == 0) 1008 fputc('\n', stdout); 1009 line[0] = '\0'; 1010 return line; 1011 } 1012 1013 cnt--; 1014 cnt = MIN(sizeof(line) - 1, cnt); 1015 (void)memcpy(line, buf, cnt); 1016 line[cnt] = '\0'; 1017 1018 return line; 1019 } 1020 1021 1022 static const char * 1023 show_prompt(EditLine *e __attribute__((unused))) 1024 { 1025 return rl_global.prompt; 1026 } 1027 1028 void 1029 init_readline(void) 1030 { 1031 HistEvent ev; 1032 const char *el_editor; 1033 const char *el_history_size; 1034 char *el_completion_keys; 1035 1036 rl_global.hist = history_init(); /* init the builtin history */ 1037 el_history_size = value("el_history_size"); 1038 if (el_history_size == NULL) 1039 el_history_size = "0"; 1040 if (history(rl_global.hist, &ev, H_SETSIZE, atoi(el_history_size))) 1041 printf("history: %s\n", ev.str); 1042 1043 rl_global.el = el_init(getprogname(), stdin, stdout, stderr); 1044 rl_global.elo = el_init(getprogname(), stdin, stdout, stderr); 1045 1046 el_editor = value("el_editor"); 1047 if (el_editor) { 1048 el_set(rl_global.el, EL_EDITOR, el_editor); 1049 el_set(rl_global.elo, EL_EDITOR, el_editor); 1050 } 1051 1052 el_set(rl_global.el, EL_PROMPT, show_prompt); 1053 el_set(rl_global.elo, EL_PROMPT, show_prompt); 1054 el_set(rl_global.el, EL_HIST, history, rl_global.hist); /* use history */ 1055 el_source(rl_global.el, NULL); /* read ~/.editrc */ 1056 1057 /* add local file completion, bind to TAB */ 1058 el_set(rl_global.el, EL_ADDFN, "mail-complete", 1059 "Context sensitive argument completion", 1060 complete); 1061 1062 el_completion_keys = value("el_completion_keys"); 1063 if (el_completion_keys && *el_completion_keys) { 1064 struct name *np, *nq; 1065 np = lexpand(el_completion_keys, 0); 1066 for (nq = np ; nq ; nq = nq->n_flink) 1067 el_set(rl_global.el, EL_BIND, nq->n_name, "mail-complete", NULL); 1068 } 1069 1070 init_complete(); 1071 1072 el_set(rl_global.el, EL_SIGNAL, 1); 1073 el_set(rl_global.elo, EL_SIGNAL, 1); 1074 1075 return; 1076 } 1077 1078 /************************************************************************/ 1079 #endif /* USE_READLINE */ 1080