1 /* $NetBSD: complete.c,v 1.10 2006/10/31 20:07:32 christos Exp $ */ 2 3 /*- 4 * Copyright (c) 1997-2000,2005,2006 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_EDITLINE 43 #undef NO_EDITCOMPLETE 44 45 #include <sys/cdefs.h> 46 #ifndef lint 47 __RCSID("$NetBSD: complete.c,v 1.10 2006/10/31 20:07:32 christos Exp $"); 48 #endif /* not lint */ 49 50 /* 51 * FTP user program - command and file completion routines 52 */ 53 54 #include <sys/stat.h> 55 56 #include <ctype.h> 57 #include <err.h> 58 #include <dirent.h> 59 #include <glob.h> 60 #include <stdio.h> 61 #include <stdlib.h> 62 #include <string.h> 63 #include <sys/param.h> 64 #include <stringlist.h> 65 #include <util.h> 66 67 #include "rcv.h" /* includes "glob.h" */ 68 #include "extern.h" 69 #include "complete.h" 70 #ifdef MIME_SUPPORT 71 #include "mime.h" 72 #endif 73 74 /* 75 * Global variables 76 */ 77 static int doglob = 1; /* glob local file names */ 78 79 #define ttyout stdout 80 #define ttywidth screenwidth /* in "glob.h" */ 81 #define ttyheight screenheight /* in "glob.h" */ 82 83 84 /* This should find the first command that matches the name given or 85 * NULL if none. Use the routine from mail in lex.c. 86 */ 87 #define getcmd(w) lex(w) 88 89 /************************************************************************/ 90 /* from src/usr.bin/ftp/utils.h (1.135) - begin */ 91 92 /* 93 * List words in stringlist, vertically arranged 94 */ 95 static void 96 list_vertical(StringList *sl) 97 { 98 int i, j; 99 int columns, lines; 100 char *p; 101 size_t w, width; 102 103 width = 0; 104 105 for (i = 0; i < sl->sl_cur; i++) { 106 w = strlen(sl->sl_str[i]); 107 if (w > width) 108 width = w; 109 } 110 width = (width + 8) &~ 7; 111 112 columns = ttywidth / width; 113 if (columns == 0) 114 columns = 1; 115 lines = (sl->sl_cur + columns - 1) / columns; 116 for (i = 0; i < lines; i++) { 117 for (j = 0; j < columns; j++) { 118 p = sl->sl_str[j * lines + i]; 119 if (p) 120 (void)fputs(p, ttyout); 121 if (j * lines + i + lines >= sl->sl_cur) { 122 (void)putc('\n', ttyout); 123 break; 124 } 125 if (p) { 126 w = strlen(p); 127 while (w < width) { 128 w = (w + 8) &~ 7; 129 (void)putc('\t', ttyout); 130 } 131 } 132 } 133 } 134 } 135 136 /* 137 * Copy characters from src into dst, \ quoting characters that require it 138 */ 139 static void 140 ftpvis(char *dst, size_t dstlen, const char *src, size_t srclen) 141 { 142 int di, si; 143 144 for (di = si = 0; 145 src[si] != '\0' && di < dstlen && si < srclen; 146 di++, si++) { 147 switch (src[si]) { 148 case '\\': 149 case ' ': 150 case '\t': 151 case '\r': 152 case '\n': 153 case '"': 154 dst[di++] = '\\'; 155 if (di >= dstlen) 156 break; 157 /* FALLTHROUGH */ 158 default: 159 dst[di] = src[si]; 160 } 161 } 162 dst[di] = '\0'; 163 } 164 165 /* 166 * sl_init() with inbuilt error checking 167 */ 168 static StringList * 169 mail_sl_init(void) 170 { 171 StringList *p; 172 173 p = sl_init(); 174 if (p == NULL) 175 err(1, "Unable to allocate memory for stringlist"); 176 return (p); 177 } 178 179 180 /* 181 * sl_add() with inbuilt error checking 182 */ 183 static void 184 mail_sl_add(StringList *sl, char *i) 185 { 186 187 if (sl_add(sl, i) == -1) 188 err(1, "Unable to add `%s' to stringlist", i); 189 } 190 191 192 /* 193 * Glob a local file name specification with the expectation of a single 194 * return value. Can't control multiple values being expanded from the 195 * expression, we return only the first. 196 * Returns NULL on error, or a pointer to a buffer containing the filename 197 * that's the caller's responsiblity to free(3) when finished with. 198 */ 199 static char * 200 globulize(const char *pattern) 201 { 202 glob_t gl; 203 int flags; 204 char *p; 205 206 if (!doglob) 207 return estrdup(pattern); 208 209 flags = GLOB_BRACE|GLOB_NOCHECK|GLOB_TILDE; 210 (void)memset(&gl, 0, sizeof(gl)); 211 if (glob(pattern, flags, NULL, &gl) || gl.gl_pathc == 0) { 212 warnx("%s: not found", pattern); 213 globfree(&gl); 214 return (NULL); 215 } 216 p = estrdup(gl.gl_pathv[0]); 217 globfree(&gl); 218 return (p); 219 } 220 221 /* from src/usr.bin/ftp/utils.h (1.135) - end */ 222 /************************************************************************/ 223 224 static int 225 comparstr(const void *a, const void *b) 226 { 227 return (strcmp(*(const char * const *)a, *(const char * const *)b)); 228 } 229 230 /* 231 * Determine if complete is ambiguous. If unique, insert. 232 * If no choices, error. If unambiguous prefix, insert that. 233 * Otherwise, list choices. words is assumed to be filtered 234 * to only contain possible choices. 235 * Args: 236 * word word which started the match 237 * dolist list by default 238 * words stringlist containing possible matches 239 * Returns a result as per el_set(EL_ADDFN, ...) 240 */ 241 static unsigned char 242 complete_ambiguous(EditLine *el, char *word, int dolist, StringList *words) 243 { 244 char insertstr[MAXPATHLEN]; 245 char *lastmatch, *p; 246 int i, j; 247 size_t matchlen, wordlen; 248 249 wordlen = strlen(word); 250 if (words->sl_cur == 0) 251 return (CC_ERROR); /* no choices available */ 252 253 if (words->sl_cur == 1) { /* only once choice available */ 254 p = words->sl_str[0] + wordlen; 255 if (*p == '\0') /* at end of word? */ 256 return (CC_REFRESH); 257 ftpvis(insertstr, sizeof(insertstr), p, strlen(p)); 258 if (el_insertstr(el, insertstr) == -1) 259 return (CC_ERROR); 260 else 261 return (CC_REFRESH); 262 } 263 264 if (!dolist) { 265 matchlen = 0; 266 lastmatch = words->sl_str[0]; 267 matchlen = strlen(lastmatch); 268 for (i = 1; i < words->sl_cur; i++) { 269 for (j = wordlen; j < strlen(words->sl_str[i]); j++) 270 if (lastmatch[j] != words->sl_str[i][j]) 271 break; 272 if (j < matchlen) 273 matchlen = j; 274 } 275 if (matchlen > wordlen) { 276 ftpvis(insertstr, sizeof(insertstr), 277 lastmatch + wordlen, matchlen - wordlen); 278 if (el_insertstr(el, insertstr) == -1) 279 return (CC_ERROR); 280 else 281 return (CC_REFRESH_BEEP); 282 } 283 } 284 285 (void)putc('\n', ttyout); 286 qsort(words->sl_str, words->sl_cur, sizeof(char *), comparstr); 287 list_vertical(words); 288 return (CC_REDISPLAY); 289 } 290 291 /* 292 * Complete a mail command. 293 */ 294 static unsigned char 295 complete_command(EditLine *el, char *word, int dolist) 296 { 297 const struct cmd *c; 298 StringList *words; 299 size_t wordlen; 300 unsigned char rv; 301 302 words = mail_sl_init(); 303 wordlen = strlen(word); 304 305 for (c = cmdtab; c->c_name != NULL; c++) { 306 if (wordlen > strlen(c->c_name)) 307 continue; 308 if (strncmp(word, c->c_name, wordlen) == 0) 309 mail_sl_add(words, __UNCONST(c->c_name)); 310 } 311 312 rv = complete_ambiguous(el, word, dolist, words); 313 if (rv == CC_REFRESH) { 314 if (el_insertstr(el, " ") == -1) 315 rv = CC_ERROR; 316 } 317 sl_free(words, 0); 318 return (rv); 319 } 320 321 /* 322 * Complete a local filename. 323 */ 324 static unsigned char 325 complete_filename(EditLine *el, char *word, int dolist) 326 { 327 StringList *words; 328 char dir[MAXPATHLEN]; 329 char *fname; 330 DIR *dd; 331 struct dirent *dp; 332 unsigned char rv; 333 size_t len; 334 335 if ((fname = strrchr(word, '/')) == NULL) { 336 dir[0] = '.'; 337 dir[1] = '\0'; 338 fname = word; 339 } else { 340 if (fname == word) { 341 dir[0] = '/'; 342 dir[1] = '\0'; 343 } else { 344 len = fname - word + 1; 345 (void)estrlcpy(dir, word, sizeof(dir)); 346 dir[len] = '\0'; 347 } 348 fname++; 349 } 350 if (dir[0] == '~') { 351 char *p; 352 353 if ((p = globulize(dir)) == NULL) 354 return (CC_ERROR); 355 (void)estrlcpy(dir, p, sizeof(dir)); 356 free(p); 357 } 358 359 if ((dd = opendir(dir)) == NULL) 360 return (CC_ERROR); 361 362 words = mail_sl_init(); 363 len = strlen(fname); 364 365 for (dp = readdir(dd); dp != NULL; dp = readdir(dd)) { 366 if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) 367 continue; 368 369 #if defined(DIRENT_MISSING_D_NAMLEN) 370 if (len > strlen(dp->d_name)) 371 continue; 372 #else 373 if (len > dp->d_namlen) 374 continue; 375 #endif 376 if (strncmp(fname, dp->d_name, len) == 0) { 377 char *tcp; 378 379 tcp = estrdup(dp->d_name); 380 mail_sl_add(words, tcp); 381 } 382 } 383 (void)closedir(dd); 384 385 rv = complete_ambiguous(el, fname, dolist, words); 386 if (rv == CC_REFRESH) { 387 struct stat sb; 388 char path[MAXPATHLEN]; 389 390 (void)estrlcpy(path, dir, sizeof(path)); 391 (void)estrlcat(path, "/", sizeof(path)); 392 (void)estrlcat(path, words->sl_str[0], sizeof(path)); 393 394 if (stat(path, &sb) >= 0) { 395 char suffix[2] = " "; 396 397 if (S_ISDIR(sb.st_mode)) 398 suffix[0] = '/'; 399 if (el_insertstr(el, suffix) == -1) 400 rv = CC_ERROR; 401 } 402 } 403 sl_free(words, 1); 404 return (rv); 405 } 406 407 static int 408 find_execs(char *word, char *path, StringList *list) 409 { 410 char *sep; 411 char *dir=path; 412 DIR *dd; 413 struct dirent *dp; 414 size_t len = strlen(word); 415 uid_t uid = getuid(); 416 gid_t gid = getgid(); 417 418 for (sep = dir; sep; dir = sep + 1) { 419 if ((sep=strchr(dir, ':')) != NULL) { 420 *sep=0; 421 } 422 423 if ((dd = opendir(dir)) == NULL) { 424 perror("dir"); 425 return -1; 426 } 427 428 for (dp = readdir(dd); dp != NULL; dp = readdir(dd)) { 429 430 if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) 431 continue; 432 433 #if defined(DIRENT_MISSING_D_NAMLEN) 434 if (len > strlen(dp->d_name)) 435 continue; 436 #else 437 if (len > dp->d_namlen) 438 continue; 439 #endif 440 441 if (strncmp(word, dp->d_name, len) == 0) { 442 struct stat sb; 443 char pathname[ MAXPATHLEN ]; 444 unsigned mask; 445 446 (void)snprintf(pathname, sizeof(pathname), 447 "%s/%s", dir, dp->d_name); 448 if (stat(pathname, &sb) != 0) { 449 perror(pathname); 450 continue; 451 } 452 453 mask = 0001; 454 if (sb.st_uid == uid) mask |= 0100; 455 if (sb.st_gid == gid) mask |= 0010; 456 457 if ((sb.st_mode & mask) != 0) { 458 char *tcp; 459 tcp = estrdup(dp->d_name); 460 mail_sl_add(list, tcp); 461 } 462 } 463 464 } 465 466 (void)closedir(dd); 467 } 468 469 return 0; 470 } 471 472 473 /* 474 * Complete a local executable 475 */ 476 static unsigned char 477 complete_executable(EditLine *el, char *word, int dolist) 478 { 479 StringList *words; 480 char dir[ MAXPATHLEN ]; 481 char *fname; 482 unsigned char rv; 483 size_t len; 484 int error; 485 486 if ((fname = strrchr(word, '/')) == NULL) { 487 dir[0] = '\0'; /* walk the path */ 488 fname = word; 489 } else { 490 if (fname == word) { 491 dir[0] = '/'; 492 dir[1] = '\0'; 493 } else { 494 len = fname - word; 495 (void)strncpy(dir, word, len); 496 dir[fname - word] = '\0'; 497 } 498 fname++; 499 } 500 501 words = sl_init(); 502 503 if (*dir == '\0') { /* walk path */ 504 char *env; 505 char *path; 506 env = getenv("PATH"); 507 len = strlen(env); 508 path = salloc(len + 1); 509 (void)strcpy(path, env); 510 error = find_execs(word, path, words); 511 } 512 else { /* check specified dir only */ 513 error = find_execs(word, dir, words); 514 } 515 if (error != 0) 516 return CC_ERROR; 517 518 rv = complete_ambiguous(el, fname, dolist, words); 519 520 if (rv == CC_REFRESH) 521 if (el_insertstr(el, " ") == -1) 522 rv = CC_ERROR; 523 524 sl_free(words, 1); 525 526 return (rv); 527 } 528 529 530 static unsigned 531 char complete_set(EditLine *el, char *word, int dolist) 532 { 533 struct var *vp; 534 char **ap; 535 char **p; 536 int h; 537 int s; 538 size_t len = strlen(word); 539 StringList *words; 540 unsigned char rv; 541 542 words = sl_init(); 543 544 /* allocate space for variables table */ 545 s = 1; 546 for (h = 0; h < HSHSIZE; h++) 547 for (vp = variables[h]; vp != NULL; vp = vp->v_link) 548 s++; 549 ap = (char **)salloc(s * sizeof *ap); 550 551 /* save the pointers */ 552 for (h = 0, p = ap; h < HSHSIZE; h++) 553 for (vp = variables[h]; vp != NULL; vp = vp->v_link) 554 *p++ = vp->v_name; 555 *p = NULL; 556 sort(ap); 557 for (p = ap; *p != NULL; p++) 558 if (len == 0 || strncmp(*p, word, len) == 0) 559 mail_sl_add(words, estrdup(*p)); 560 561 rv = complete_ambiguous(el, word, dolist, words); 562 563 sl_free(words, 1); 564 565 return(rv); 566 } 567 568 569 static unsigned char 570 complete_alias(EditLine *el, char *word, int dolist) 571 { 572 struct grouphead *gh; 573 char **ap; 574 char **p; 575 int h; 576 int s; 577 size_t len = strlen(word); 578 StringList *words; 579 unsigned char rv; 580 581 words = sl_init(); 582 583 /* allocate space for alias table */ 584 s = 1; 585 for (h = 0; h < HSHSIZE; h++) 586 for (gh = groups[h]; gh != NULL; gh = gh->g_link) 587 s++; 588 ap = (char **)salloc(s * sizeof *ap); 589 590 /* save pointers */ 591 p = ap; 592 for (h = 0; h < HSHSIZE; h++) 593 for (gh = groups[h]; gh != NULL; gh = gh->g_link) 594 *p++ = gh->g_name; 595 *p = NULL; 596 sort(ap); 597 for (p = ap; *p != NULL; p++) 598 if (len == 0 || strncmp(*p, word, len) == 0) 599 mail_sl_add(words, estrdup(*p)); 600 601 rv = complete_ambiguous(el, word, dolist, words); 602 if (rv == CC_REFRESH) 603 if (el_insertstr(el, " ") == -1) 604 rv = CC_ERROR; 605 606 sl_free(words, 1); 607 608 return(rv); 609 } 610 611 612 #ifdef SMOPTS_CMD 613 static unsigned 614 char complete_smopts(EditLine *el, char *word, int dolist) 615 { 616 struct grouphead *gh; 617 struct smopts_s *sp; 618 char **ap; 619 char **p; 620 int h; 621 int s1; 622 int s2; 623 size_t len; 624 StringList *words; 625 unsigned char rv; 626 627 len = strlen(word); 628 words = sl_init(); 629 630 /* count the entries in the smoptstbl and groups (alias) tables */ 631 s1 = 1; 632 s2 = 1; 633 for (h = 0; h < HSHSIZE; h++) { 634 for (sp = smoptstbl[h]; sp != NULL; sp = sp->s_link) 635 s1++; 636 for (gh = groups[h]; gh != NULL; gh = gh->g_link) 637 s2++; 638 } 639 640 /* allocate sufficient space for the pointers */ 641 ap = (char **)salloc(MAX(s1, s2) * sizeof *ap); 642 643 /* 644 * First do the smoptstbl pointers. (case _insensitive_) 645 */ 646 p = ap; 647 for (h = 0; h < HSHSIZE; h++) 648 for (sp = smoptstbl[h]; sp != NULL; sp = sp->s_link) 649 *p++ = sp->s_name; 650 *p = NULL; 651 sort(ap); 652 for (p = ap; *p != NULL; p++) 653 if (len == 0 || strncasecmp(*p, word, len) == 0) 654 mail_sl_add(words, estrdup(*p)); 655 656 /* 657 * Now do the groups (alias) pointers. (case sensitive) 658 */ 659 p = ap; 660 for (h = 0; h < HSHSIZE; h++) 661 for (gh = groups[h]; gh != NULL; gh = gh->g_link) 662 *p++ = gh->g_name; 663 *p = NULL; 664 sort(ap); 665 for (p = ap; *p != NULL; p++) 666 if (len == 0 || strncmp(*p, word, len) == 0) 667 mail_sl_add(words, estrdup(*p)); 668 669 rv = complete_ambiguous(el, word, dolist, words); 670 671 sl_free(words, 1); 672 673 return(rv); 674 } 675 #endif /* SMOPTS_CMD */ 676 677 678 static char *stringbase; /* current scan point in line buffer */ 679 static char *argbase; /* current storage point in arg buffer */ 680 static StringList *marg_sl; /* stringlist containing margv */ 681 static int margc; /* count of arguments on input line */ 682 #define margv (marg_sl->sl_str) /* args parsed from input line */ 683 684 static char *cursor_pos; 685 static int cursor_argc; 686 static int cursor_argo; 687 688 static void 689 init_complete(void) 690 { 691 marg_sl = sl_init(); 692 return; 693 } 694 695 696 697 /************************************************************************/ 698 /* from /usr/src/usr.bin/ftp/main.c(1.101) - begin */ 699 700 static int slrflag; 701 #ifdef NEED_ALTARG 702 static char *altarg; /* argv[1] with no shell-like preprocessing */ 703 #endif 704 705 #ifdef NO_EDITCOMPLETE 706 #define INC_CHKCURSOR(x) (x)++ 707 #else /* !NO_EDITCOMPLETE */ 708 #define INC_CHKCURSOR(x) \ 709 do { \ 710 (x)++; \ 711 if (x == cursor_pos) { \ 712 cursor_argc = margc; \ 713 cursor_argo = ap - argbase; \ 714 cursor_pos = NULL; \ 715 } \ 716 } while(/* CONSTCOND */ 0) 717 #endif /* !NO_EDITCOMPLETE */ 718 719 /* 720 * Parse string into argbuf; 721 * implemented with FSM to 722 * handle quoting and strings 723 */ 724 static char * 725 slurpstring(void) 726 { 727 int got_one = 0; 728 char *sb = stringbase; 729 char *ap = argbase; 730 char *tmp = argbase; /* will return this if token found */ 731 732 if (*sb == '!' || *sb == '$') { /* recognize ! as a token for shell */ 733 switch (slrflag) { /* and $ as token for macro invoke */ 734 case 0: 735 slrflag++; 736 INC_CHKCURSOR(stringbase); 737 return __UNCONST((*sb == '!') ? "!" : "$"); 738 /* NOTREACHED */ 739 case 1: 740 slrflag++; 741 #ifdef NEED_ALTARG 742 altarg = stringbase; 743 #endif 744 break; 745 default: 746 break; 747 } 748 } 749 750 S0: 751 switch (*sb) { 752 753 case '\0': 754 goto OUT; 755 756 case ' ': 757 case '\t': 758 INC_CHKCURSOR(sb); 759 goto S0; 760 761 default: 762 switch (slrflag) { 763 case 0: 764 slrflag++; 765 break; 766 case 1: 767 slrflag++; 768 #ifdef NEED_ALTARG 769 altarg = sb; 770 #endif 771 break; 772 default: 773 break; 774 } 775 goto S1; 776 } 777 778 S1: 779 switch (*sb) { 780 781 case ' ': 782 case '\t': 783 case '\0': 784 goto OUT; /* end of token */ 785 786 case '\\': 787 INC_CHKCURSOR(sb); 788 goto S2; /* slurp next character */ 789 790 case '"': 791 INC_CHKCURSOR(sb); 792 goto S3; /* slurp quoted string */ 793 794 default: 795 /* the first arg (command) is special - see execute() in lex.c */ 796 if (margc == 0 && got_one && 797 strchr(" \t0123456789$^.:/-+*'\"", *sb)) 798 goto OUT; 799 800 *ap = *sb; /* add character to token */ 801 ap++; 802 INC_CHKCURSOR(sb); 803 got_one = 1; 804 goto S1; 805 } 806 807 S2: 808 switch (*sb) { 809 810 case '\0': 811 goto OUT; 812 813 default: 814 *ap = *sb; 815 ap++; 816 INC_CHKCURSOR(sb); 817 got_one = 1; 818 goto S1; 819 } 820 821 S3: 822 switch (*sb) { 823 824 case '\0': 825 goto OUT; 826 827 case '"': 828 INC_CHKCURSOR(sb); 829 goto S1; 830 831 default: 832 *ap = *sb; 833 ap++; 834 INC_CHKCURSOR(sb); 835 got_one = 1; 836 goto S3; 837 } 838 839 OUT: 840 if (got_one) 841 *ap++ = '\0'; 842 argbase = ap; /* update storage pointer */ 843 stringbase = sb; /* update scan pointer */ 844 if (got_one) { 845 return (tmp); 846 } 847 switch (slrflag) { 848 case 0: 849 slrflag++; 850 break; 851 case 1: 852 slrflag++; 853 #ifdef NEED_ALTARG 854 altarg = NULL; 855 #endif 856 break; 857 default: 858 break; 859 } 860 return (NULL); 861 } 862 863 864 /* 865 * Slice a string up into argc/argv. 866 */ 867 static void 868 makeargv(char *line) 869 { 870 static char argbuf[ LINESIZE ]; /* argument storage buffer */ 871 char *argp; 872 873 stringbase = line; /* scan from first of buffer */ 874 argbase = argbuf; /* store from first of buffer */ 875 slrflag = 0; 876 marg_sl->sl_cur = 0; /* reset to start of marg_sl */ 877 for (margc = 0; /* EMPTY */; margc++) { 878 argp = slurpstring(); 879 mail_sl_add(marg_sl, argp); 880 if (argp == NULL) 881 break; 882 } 883 #ifndef NO_EDITCOMPLETE 884 if (cursor_pos == line) { 885 cursor_argc = 0; 886 cursor_argo = 0; 887 } else if (cursor_pos != NULL) { 888 cursor_argc = margc; 889 cursor_argo = strlen(margv[margc-1]); 890 } 891 #endif /* !NO_EDITCOMPLETE */ 892 } 893 894 /* from /usr/src/usr.bin/ftp/main.c(1.101) - end */ 895 /************************************************************************/ 896 897 /* Some people like to bind file completion to CTRL-D. In emacs mode, 898 * CTRL-D is also used to delete the current character, we have to 899 * special case this situation. 900 */ 901 #define EMACS_CTRL_D_BINDING_HACK 902 903 #ifdef EMACS_CTRL_D_BINDING_HACK 904 static int 905 is_emacs_mode(EditLine *el) 906 { 907 char *mode; 908 if (el_get(el, EL_EDITOR, &mode) == -1) 909 return 0; 910 return equal(mode, "emacs"); 911 } 912 913 static int 914 emacs_ctrl_d(EditLine *el, const LineInfo *lf, int ch, size_t len) 915 { 916 static char delunder[3] = { CTRL('f'), CTRL('h'), '\0' }; 917 if (ch == CTRL('d') && is_emacs_mode(el)) { /* CTRL-D is special */ 918 if (len == 0) 919 return (CC_EOF); 920 if (lf->lastchar != lf->cursor) { /* delete without using ^D */ 921 el_push(el, delunder); /* ^F^H */ 922 return (CC_NORM); 923 } 924 } 925 return -1; 926 } 927 #endif /* EMACS_CTRL_D_BINDING_HACK */ 928 929 /* 930 * Generic complete routine 931 */ 932 static unsigned char 933 mail_complete(EditLine *el, int ch) 934 { 935 static char line[LINESIZE]; /* input line buffer */ 936 static char word[LINESIZE]; 937 static int lastc_argc, lastc_argo; 938 939 const struct cmd *c; 940 const LineInfo *lf; 941 int celems, dolist, cmpltype; 942 size_t len; 943 944 lf = el_line(el); 945 len = lf->lastchar - lf->buffer; 946 947 #ifdef EMACS_CTRL_D_BINDING_HACK 948 { 949 int cc_ret; 950 if ((cc_ret = emacs_ctrl_d(el, lf, ch, len)) != -1) 951 return cc_ret; 952 } 953 #endif /* EMACS_CTRL_D_BINDING_HACK */ 954 955 if (len >= sizeof(line) - 1) 956 return (CC_ERROR); 957 958 (void)strlcpy(line, lf->buffer, len + 1); /* do not use estrlcpy here! */ 959 cursor_pos = line + (lf->cursor - lf->buffer); 960 lastc_argc = cursor_argc; /* remember last cursor pos */ 961 lastc_argo = cursor_argo; 962 makeargv(line); /* build argc/argv of current line */ 963 964 if (cursor_argo >= sizeof(word) - 1) 965 return (CC_ERROR); 966 967 dolist = 0; 968 /* if cursor and word are the same, list alternatives */ 969 if (lastc_argc == cursor_argc && lastc_argo == cursor_argo 970 && strncmp(word, margv[cursor_argc] ? margv[cursor_argc] : "", 971 (size_t)cursor_argo) == 0) 972 dolist = 1; 973 else if (cursor_argc < margc) 974 (void)strlcpy(word, margv[cursor_argc], (size_t)cursor_argo + 1); /* do not use estrlcpy() here */ 975 word[cursor_argo] = '\0'; 976 977 if (cursor_argc == 0) 978 return (complete_command(el, word, dolist)); 979 980 c = getcmd(margv[0]); 981 if (c == NULL) 982 return (CC_ERROR); 983 celems = strlen(c->c_complete); 984 985 /* check for 'continuation' completes (which are uppercase) */ 986 if ((cursor_argc > celems) && (celems > 0) 987 && isupper((unsigned char) c->c_complete[celems-1])) 988 cursor_argc = celems; 989 990 if (cursor_argc > celems) 991 return (CC_ERROR); 992 993 cmpltype = c->c_complete[cursor_argc - 1]; 994 switch (cmpltype) { 995 case 'a': /* alias complete */ 996 case 'A': 997 return (complete_alias(el, word, dolist)); 998 999 case 'c': /* command complete */ 1000 case 'C': 1001 return (complete_command(el, word, dolist)); 1002 1003 case 'f': /* filename complete */ 1004 case 'F': 1005 return (complete_filename(el, word, dolist)); 1006 #ifdef SMOPTS_CMD 1007 case 'm': 1008 case 'M': 1009 return (complete_smopts(el, word, dolist)); 1010 #endif 1011 case 'n': /* no complete */ 1012 case 'N': /* no complete */ 1013 return (CC_ERROR); 1014 1015 case 's': 1016 case 'S': 1017 return (complete_set(el, word, dolist)); 1018 1019 case 'x': /* executable complete */ 1020 case 'X': 1021 return (complete_executable(el, word, dolist)); 1022 1023 default: 1024 errx(1, "unknown complete type `%c'", cmpltype); 1025 return (CC_ERROR); 1026 } 1027 /* NOTREACHED */ 1028 } 1029 1030 1031 /* 1032 * Generic file completion routine 1033 */ 1034 static unsigned char 1035 file_complete(EditLine *el, int ch) 1036 { 1037 static char word[LINESIZE]; 1038 1039 const LineInfo *lf; 1040 size_t len, word_len; 1041 1042 lf = el_line(el); 1043 len = lf->lastchar - lf->buffer; 1044 1045 #ifdef EMACS_CTRL_D_BINDING_HACK 1046 { 1047 int cc_ret; 1048 if ((cc_ret = emacs_ctrl_d(el, lf, ch, len)) != -1) 1049 return cc_ret; 1050 } 1051 #endif /* EMACS_CTRL_D_BINDING_HACK */ 1052 1053 if (len >= sizeof(word) - 1) 1054 return (CC_ERROR); 1055 1056 word_len = lf->cursor - lf->buffer; 1057 (void)strlcpy(word, lf->buffer, word_len + 1); /* do not use estrlcpy here! */ 1058 return complete_filename(el, word, len != word_len); 1059 } 1060 1061 1062 #ifdef MIME_SUPPORT 1063 /* 1064 * Complete mime_transfer_encoding type 1065 */ 1066 static unsigned char 1067 mime_enc_complete(EditLine *el, int ch) 1068 { 1069 static char word[LINESIZE]; 1070 StringList *words; 1071 unsigned char rv; 1072 const LineInfo *lf; 1073 size_t len, word_len; 1074 1075 lf = el_line(el); 1076 len = lf->lastchar - lf->buffer; 1077 1078 #ifdef EMACS_CTRL_D_BINDING_HACK 1079 { 1080 int cc_ret; 1081 if ((cc_ret = emacs_ctrl_d(el, lf, ch, len)) != -1) 1082 return cc_ret; 1083 } 1084 #endif /* EMACS_CTRL_D_BINDING_HACK */ 1085 1086 if (len >= sizeof(word) - 1) 1087 return (CC_ERROR); 1088 1089 words = mail_sl_init(); 1090 word_len = lf->cursor - lf->buffer; 1091 { 1092 const char *ename; 1093 const void *cookie; 1094 cookie = NULL; 1095 for (ename = mime_next_encoding_name(&cookie); 1096 ename; 1097 ename = mime_next_encoding_name(&cookie)) 1098 if (word_len == 0 || 1099 strncmp(lf->buffer, ename, word_len) == 0) { 1100 char *cp; 1101 cp = estrdup(ename); 1102 mail_sl_add(words, cp); 1103 } 1104 } 1105 (void)strlcpy(word, lf->buffer, word_len + 1); /* do not use estrlcpy here */ 1106 1107 rv = complete_ambiguous(el, word, len != word_len, words); 1108 1109 sl_free(words, 1); 1110 return (rv); 1111 } 1112 #endif /* MIME_SUPPORT */ 1113 1114 /************************************************************************* 1115 * Our public interface to el_gets(): 1116 * 1117 * init_editline() 1118 * Initializes of all editline and completion data strutures. 1119 * 1120 * my_gets() 1121 * Returns the next line of input as a NULL termnated string without 1122 * the trailing newline. 1123 */ 1124 1125 static const char *el_prompt; 1126 1127 /*ARGSUSED*/ 1128 static const char * 1129 show_prompt(EditLine *e __unused) 1130 { 1131 return el_prompt; 1132 } 1133 1134 char * 1135 my_gets(el_mode_t *em, const char *prompt, char *string) 1136 { 1137 int cnt; 1138 size_t len; 1139 const char *buf; 1140 HistEvent ev; 1141 static char line[LINE_MAX]; 1142 1143 el_prompt = prompt; 1144 1145 if (string) 1146 el_push(em->el, string); 1147 1148 buf = el_gets(em->el, &cnt); 1149 1150 if (buf == NULL || cnt <= 0) { 1151 if (cnt == 0) 1152 (void)putc('\n', stdout); 1153 return NULL; 1154 } 1155 1156 cnt--; /* trash the trailing LF */ 1157 len = MIN(sizeof(line) - 1, cnt); 1158 (void)memcpy(line, buf, len); 1159 line[cnt] = '\0'; 1160 1161 /* enter non-empty lines into history */ 1162 if (em->hist) { 1163 const char *p; 1164 p = skip_white(line); 1165 if (*p && history(em->hist, &ev, H_ENTER, line) == 0) 1166 (void)printf("Failed history entry: %s", line); 1167 } 1168 return line; 1169 } 1170 1171 static el_mode_t 1172 init_el_mode( 1173 const char *el_editor, 1174 unsigned char (*completer)(EditLine *, int), 1175 struct name *keys, 1176 int history_size) 1177 { 1178 el_mode_t em; 1179 (void)memset(&em, 0, sizeof(em)); 1180 1181 em.el = el_init(getprogname(), stdin, stdout, stderr); 1182 1183 (void)el_set(em.el, EL_PROMPT, show_prompt); 1184 (void)el_set(em.el, EL_SIGNAL, SIGHUP); 1185 1186 if (el_editor) 1187 (void)el_set(em.el, EL_EDITOR, el_editor); 1188 1189 if (completer) { 1190 struct name *np; 1191 (void)el_set(em.el, EL_ADDFN, "mail-complete", 1192 "Context sensitive argument completion", completer); 1193 for (np = keys; np; np = np->n_flink) 1194 (void)el_set(em.el, EL_BIND, np->n_name, 1195 "mail-complete", NULL); 1196 } 1197 1198 if (history_size) { 1199 HistEvent ev; 1200 em.hist = history_init(); 1201 if (history(em.hist, &ev, H_SETSIZE, history_size)) 1202 (void)printf("history: %s\n", ev.str); 1203 (void)el_set(em.el, EL_HIST, history, em.hist); 1204 } 1205 1206 (void)el_source(em.el, NULL); /* read ~/.editrc */ 1207 1208 return em; 1209 } 1210 1211 1212 struct el_modes_s elm = { 1213 .command = { .el = NULL, .hist = NULL, }, 1214 .string = { .el = NULL, .hist = NULL, }, 1215 .filec = { .el = NULL, .hist = NULL, }, 1216 #ifdef MIME_SUPPORT 1217 .mime_enc = { .el = NULL, .hist = NULL, }, 1218 #endif 1219 }; 1220 1221 void 1222 init_editline(void) 1223 { 1224 const char *mode; 1225 int hist_size; 1226 struct name *keys; 1227 char *cp; 1228 1229 mode = value(ENAME_EL_EDITOR); 1230 1231 cp = value(ENAME_EL_HISTORY_SIZE); 1232 hist_size = cp ? atoi(cp) : 0; 1233 1234 cp = value(ENAME_EL_COMPLETION_KEYS); 1235 keys = cp && *cp ? lexpand(cp, 0) : NULL; 1236 1237 elm.command = init_el_mode(mode, mail_complete, keys, hist_size); 1238 elm.filec = init_el_mode(mode, file_complete, keys, 0); 1239 elm.string = init_el_mode(mode, NULL, NULL, 0); 1240 #ifdef MIME_SUPPORT 1241 elm.mime_enc = init_el_mode(mode, mime_enc_complete, keys, 0); 1242 #endif 1243 init_complete(); 1244 1245 return; 1246 } 1247 1248 #endif /* USE_EDITLINE */ 1249