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