1 /* $NetBSD: complete.c,v 1.20 2010/01/12 14:44:24 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 * 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 38 #include <sys/cdefs.h> 39 #ifndef lint 40 __RCSID("$NetBSD: complete.c,v 1.20 2010/01/12 14:44:24 christos Exp $"); 41 #endif /* not lint */ 42 43 /* 44 * FTP user program - command and file completion routines 45 */ 46 47 #include <assert.h> 48 #include <ctype.h> 49 #include <err.h> 50 #include <dirent.h> 51 #include <glob.h> 52 #include <stdio.h> 53 #include <stdlib.h> 54 #include <string.h> 55 #include <stringlist.h> 56 #include <termcap.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 #include "sig.h" 69 #ifdef THREAD_SUPPORT 70 #include "thread.h" 71 #endif 72 73 #define BELL 0x7 74 75 /* 76 * Global variables 77 */ 78 static int doglob = 1; /* glob local file names */ 79 80 #define ttyout stdout 81 #define ttywidth screenwidth /* in "glob.h" */ 82 #define ttyheight screenheight /* in "glob.h" */ 83 84 /************************************************************************/ 85 /* from src/usr.bin/ftp/utils.h (1.135) - begin */ 86 87 /* 88 * List words in stringlist, vertically arranged 89 */ 90 static void 91 list_vertical(StringList *sl) 92 { 93 int k; 94 size_t i, j, columns, lines; 95 char *p; 96 size_t w, width; 97 98 width = 0; 99 100 for (i = 0; i < sl->sl_cur; i++) { 101 w = strlen(sl->sl_str[i]); 102 if (w > width) 103 width = w; 104 } 105 width = (width + 8) &~ 7; 106 107 columns = ttywidth / width; 108 if (columns == 0) 109 columns = 1; 110 lines = (sl->sl_cur + columns - 1) / columns; 111 k = 0; 112 for (i = 0; i < lines; i++) { 113 for (j = 0; j < columns; j++) { 114 p = sl->sl_str[j * lines + i]; 115 if (p) 116 (void)fputs(p, ttyout); 117 if (j * lines + i + lines >= sl->sl_cur) { 118 (void)putc('\n', ttyout); 119 break; 120 } 121 if (p) { 122 w = strlen(p); 123 while (w < width) { 124 w = (w + 8) &~ 7; 125 (void)putc('\t', ttyout); 126 } 127 } 128 } 129 if (ttyheight > 2 && ++k == ttyheight - 2) { 130 int ch; 131 k = 0; 132 (void)fputs("--more--", ttyout); 133 while ((ch = getchar()) != EOF && ch != ' ' && ch != 'q') 134 (void)putc(BELL, ttyout); 135 (void)fputs("\r \r", ttyout); 136 if (ch == 'q') 137 break; 138 } 139 } 140 } 141 142 /* 143 * Copy characters from src into dst, \ quoting characters that require it 144 */ 145 static void 146 ftpvis(char *dst, size_t dstlen, const char *src, size_t srclen) 147 { 148 size_t di, si; 149 150 for (di = si = 0; 151 src[si] != '\0' && di < dstlen && si < srclen; 152 di++, si++) { 153 switch (src[si]) { 154 case '\\': 155 case ' ': 156 case '\t': 157 case '\r': 158 case '\n': 159 case '"': 160 dst[di++] = '\\'; 161 if (di >= dstlen) 162 break; 163 /* FALLTHROUGH */ 164 default: 165 dst[di] = src[si]; 166 } 167 } 168 dst[di] = '\0'; 169 } 170 171 /* 172 * sl_init() with inbuilt error checking 173 */ 174 static StringList * 175 mail_sl_init(void) 176 { 177 StringList *p; 178 179 p = sl_init(); 180 if (p == NULL) 181 err(EXIT_FAILURE, "Unable to allocate memory for stringlist"); 182 return p; 183 } 184 185 186 /* 187 * sl_add() with inbuilt error checking 188 */ 189 static void 190 mail_sl_add(StringList *sl, char *i) 191 { 192 193 if (sl_add(sl, i) == -1) 194 err(EXIT_FAILURE, "Unable to add `%s' to stringlist", i); 195 } 196 197 198 /* 199 * Glob a local file name specification with the expectation of a single 200 * return value. Can't control multiple values being expanded from the 201 * expression, we return only the first. 202 * Returns NULL on error, or a pointer to a buffer containing the filename 203 * that's the caller's responsiblity to free(3) when finished with. 204 */ 205 static char * 206 globulize(const char *pattern) 207 { 208 glob_t gl; 209 int flags; 210 char *p; 211 212 if (!doglob) 213 return estrdup(pattern); 214 215 flags = GLOB_BRACE|GLOB_NOCHECK|GLOB_TILDE; 216 (void)memset(&gl, 0, sizeof(gl)); 217 if (glob(pattern, flags, NULL, &gl) || gl.gl_pathc == 0) { 218 warnx("%s: not found", pattern); 219 globfree(&gl); 220 return NULL; 221 } 222 p = estrdup(gl.gl_pathv[0]); 223 globfree(&gl); 224 return p; 225 } 226 227 /* from src/usr.bin/ftp/utils.h (1.135) - end */ 228 /************************************************************************/ 229 230 static int 231 comparstr(const void *a, const void *b) 232 { 233 return strcmp(*(const char * const *)a, *(const char * const *)b); 234 } 235 236 /* 237 * Determine if complete is ambiguous. If unique, insert. 238 * If no choices, error. If unambiguous prefix, insert that. 239 * Otherwise, list choices. words is assumed to be filtered 240 * to only contain possible choices. 241 * Args: 242 * word word which started the match 243 * dolist list by default 244 * words stringlist containing possible matches 245 * Returns a result as per el_set(EL_ADDFN, ...) 246 */ 247 static unsigned char 248 complete_ambiguous(EditLine *el, char *word, int dolist, StringList *words) 249 { 250 char insertstr[MAXPATHLEN]; 251 char *lastmatch, *p; 252 size_t i, j, 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 742 if (el_get(el, EL_EDITOR, &mode) == -1) 743 return 0; 744 return equal(mode, "emacs"); 745 } 746 747 static int 748 emacs_ctrl_d(EditLine *el, const LineInfo *lf, int ch) 749 { 750 static char delunder[3] = { CTRL('f'), CTRL('h'), '\0' }; 751 752 if (ch == CTRL('d') && is_emacs_mode(el)) { /* CTRL-D is special */ 753 if (lf->buffer == lf->lastchar) 754 return CC_EOF; 755 if (lf->cursor != lf->lastchar) { /* delete without using ^D */ 756 el_push(el, delunder); /* ^F^H */ 757 return CC_NORM; 758 } 759 } 760 return -1; 761 } 762 #endif /* EMACS_CTRL_D_BINDING_HACK */ 763 764 /* 765 * Check if this is the second request made for this line indicating 766 * the need to list all the completion possibilities. 767 */ 768 static int 769 get_dolist(const LineInfo *lf) 770 { 771 static char last_line[LINESIZE]; 772 static char *last_cursor_pos; 773 char *cursor_pos; 774 int dolist; 775 size_t len; 776 777 len = lf->lastchar - lf->buffer; 778 if (len >= sizeof(last_line) - 1) 779 return -1; 780 781 cursor_pos = last_line + (lf->cursor - lf->buffer); 782 dolist = 783 cursor_pos == last_cursor_pos && 784 strncmp(last_line, lf->buffer, len) == 0; 785 786 (void)strlcpy(last_line, lf->buffer, len + 1); 787 last_cursor_pos = cursor_pos; 788 789 return dolist; 790 } 791 792 /* 793 * Take the full line (lf) including the command and split it into a 794 * sub-line (returned) and a completion context (cmplarray). 795 */ 796 static LineInfo * 797 split_line(const char **cmplarray, const LineInfo *lf) 798 { 799 static LineInfo li; 800 const struct cmd *c; 801 char *cmdname; 802 char line[LINESIZE]; 803 char *cp; 804 size_t len; 805 806 len = lf->cursor - lf->buffer; 807 if (len + 1 > sizeof(line)) 808 return NULL; 809 810 (void)strlcpy(line, lf->buffer, len + 1); 811 812 li.cursor = line + len; 813 li.lastchar = line + len; 814 815 cp = skip_WSP(line); 816 cmdname = get_cmdname(cp); 817 cp += strlen(cmdname); 818 819 if (cp == li.cursor) { 820 *cmplarray = "c"; 821 li.buffer = cmdname; 822 return &li; 823 } 824 825 c = lex(cmdname); 826 if (c == NULL) 827 return NULL; 828 829 *cmplarray = c->c_complete; 830 if (c->c_pipe) { 831 char *cp2; 832 if ((cp2 = shellpr(cp)) != NULL) { 833 cp = cp2; 834 # define XX(a) ((a) + ((a)[1] == '>' ? 2 : 1)) 835 while ((cp2 = shellpr(XX(cp))) != NULL) 836 cp = cp2; 837 838 if (*cp == '|') { 839 *cmplarray = "xF"; 840 cp = skip_WSP(cp + 1); 841 } 842 else { 843 assert(*cp == '>'); 844 cp = skip_WSP(XX(cp)); 845 *cmplarray = "f"; 846 } 847 # undef XX 848 } 849 } 850 li.buffer = cp; 851 return &li; 852 } 853 854 /* 855 * Split a sub-line and a completion context into a word and a 856 * completion type. Use the editline tokenizer to handle the quoting 857 * and splitting. 858 */ 859 static char * 860 split_word(int *cmpltype, const char *cmplarray, LineInfo *li) 861 { 862 static Tokenizer *t = NULL; 863 const char **argv; 864 char *word; 865 int argc; 866 int cursorc; 867 int cursoro; 868 int arraylen; 869 870 if (t != NULL) 871 tok_reset(t); 872 else { 873 if ((t = tok_init(NULL)) == NULL) 874 err(EXIT_FAILURE, "tok_init"); 875 } 876 if (tok_line(t, li, &argc, &argv, &cursorc, &cursoro) == -1) 877 err(EXIT_FAILURE, "tok_line"); 878 879 if (cursorc >= argc) 880 word = __UNCONST(""); 881 else { 882 word = salloc((size_t)cursoro + 1); 883 (void)strlcpy(word, argv[cursorc], (size_t)cursoro + 1); 884 } 885 886 /* check for 'continuation' completes (which are uppercase) */ 887 arraylen = (int)strlen(cmplarray); 888 if (cursorc >= arraylen && 889 arraylen > 0 && 890 isupper((unsigned char)cmplarray[arraylen - 1])) 891 cursorc = arraylen - 1; 892 893 if (cursorc >= arraylen) 894 return NULL; 895 896 *cmpltype = cmplarray[cursorc]; 897 return word; 898 } 899 900 /* 901 * A generic complete routine for the mail command line. 902 */ 903 static unsigned char 904 mail_complete(EditLine *el, int ch) 905 { 906 LineInfo *li; 907 const LineInfo *lf; 908 const char *cmplarray; 909 int dolist; 910 int cmpltype; 911 char *word; 912 913 lf = el_line(el); 914 915 #ifdef EMACS_CTRL_D_BINDING_HACK 916 { 917 int cc_ret; 918 if ((cc_ret = emacs_ctrl_d(el, lf, ch)) != -1) 919 return cc_ret; 920 } 921 #endif /* EMACS_CTRL_D_BINDING_HACK */ 922 923 if ((dolist = get_dolist(lf)) == -1) 924 return CC_ERROR; 925 926 if ((li = split_line(&cmplarray, lf)) == NULL) 927 return CC_ERROR; 928 929 if ((word = split_word(&cmpltype, cmplarray, li)) == NULL) 930 return CC_ERROR; 931 932 switch (cmpltype) { 933 case 'a': /* alias complete */ 934 case 'A': 935 return complete_alias(el, word, dolist); 936 937 case 'c': /* command complete */ 938 case 'C': 939 return complete_command(el, word, dolist); 940 941 case 'f': /* filename complete */ 942 case 'F': 943 return complete_filename(el, word, dolist); 944 945 case 'm': 946 case 'M': 947 return complete_smopts(el, word, dolist); 948 949 case 'n': /* no complete */ 950 case 'N': /* no complete */ 951 return CC_ERROR; 952 953 case 's': 954 case 'S': 955 return complete_set(el, word, dolist); 956 #ifdef THREAD_SUPPORT 957 case 't': 958 case 'T': 959 return complete_thread_key(el, word, dolist); 960 #endif 961 case 'x': /* executable complete */ 962 case 'X': 963 return complete_executable(el, word, dolist); 964 965 default: 966 warnx("unknown complete type `%c'", cmpltype); 967 #if 0 968 assert(/*CONSTCOND*/0); 969 #endif 970 return CC_ERROR; 971 } 972 /* NOTREACHED */ 973 } 974 975 976 /* 977 * A generic file completion routine. 978 */ 979 static unsigned char 980 file_complete(EditLine *el, int ch) 981 { 982 static char word[LINESIZE]; 983 const LineInfo *lf; 984 size_t word_len; 985 int dolist; 986 987 lf = el_line(el); 988 989 #ifdef EMACS_CTRL_D_BINDING_HACK 990 { 991 int cc_ret; 992 if ((cc_ret = emacs_ctrl_d(el, lf, ch)) != -1) 993 return cc_ret; 994 } 995 #endif /* EMACS_CTRL_D_BINDING_HACK */ 996 997 word_len = lf->cursor - lf->buffer; 998 if (word_len + 1 > sizeof(word)) 999 return CC_ERROR; 1000 1001 (void)strlcpy(word, lf->buffer, word_len + 1); /* do not use estrlcpy here! */ 1002 1003 if ((dolist = get_dolist(lf)) == -1) 1004 return CC_ERROR; 1005 1006 return complete_filename(el, word, dolist); 1007 } 1008 1009 1010 #ifdef MIME_SUPPORT 1011 /* 1012 * Complete mime_transfer_encoding type. 1013 */ 1014 static unsigned char 1015 mime_enc_complete(EditLine *el, int ch) 1016 { 1017 static char word[LINESIZE]; 1018 StringList *words; 1019 unsigned char rv; 1020 const LineInfo *lf; 1021 size_t word_len; 1022 int dolist; 1023 1024 lf = el_line(el); 1025 1026 #ifdef EMACS_CTRL_D_BINDING_HACK 1027 { 1028 int cc_ret; 1029 if ((cc_ret = emacs_ctrl_d(el, lf, ch)) != -1) 1030 return cc_ret; 1031 } 1032 #endif /* EMACS_CTRL_D_BINDING_HACK */ 1033 1034 word_len = lf->cursor - lf->buffer; 1035 if (word_len >= sizeof(word) - 1) 1036 return CC_ERROR; 1037 1038 words = mail_sl_init(); 1039 { 1040 const char *ename; 1041 const void *cookie; 1042 cookie = NULL; 1043 for (ename = mime_next_encoding_name(&cookie); 1044 ename; 1045 ename = mime_next_encoding_name(&cookie)) 1046 if (word_len == 0 || 1047 strncmp(lf->buffer, ename, word_len) == 0) { 1048 char *cp; 1049 cp = estrdup(ename); 1050 mail_sl_add(words, cp); 1051 } 1052 } 1053 (void)strlcpy(word, lf->buffer, word_len + 1); 1054 1055 if ((dolist = get_dolist(lf)) == -1) 1056 return CC_ERROR; 1057 1058 rv = complete_ambiguous(el, word, dolist, words); 1059 1060 sl_free(words, 1); 1061 return rv; 1062 } 1063 #endif /* MIME_SUPPORT */ 1064 1065 1066 /************************************************************************* 1067 * Our public interface to el_gets(): 1068 * 1069 * init_editline() 1070 * Initializes of all editline and completion data strutures. 1071 * 1072 * my_gets() 1073 * Displays prompt, calls el_gets() and deals with history. 1074 * Returns the next line of input as a NULL termnated string 1075 * without the trailing newline, or NULL if el_gets() sees is an 1076 * error or signal. 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 /* 1089 * Write the current INTR character to fp in a friendly form. 1090 */ 1091 static void 1092 echo_INTR(void *p) 1093 { 1094 struct termios ttybuf; 1095 char buf[5]; 1096 FILE *fp; 1097 1098 fp = p; 1099 if (tcgetattr(fileno(stdin), &ttybuf) == -1) 1100 warn("tcgetattr"); 1101 else { 1102 (void)vis(buf, ttybuf.c_cc[VINTR], VIS_SAFE | VIS_NOSLASH, 0); 1103 (void)fprintf(fp, "%s", buf); 1104 (void)fflush(fp); 1105 } 1106 } 1107 1108 static sig_t old_sigint; 1109 static void 1110 comp_intr(int signo) 1111 { 1112 1113 echo_INTR(stdout); 1114 old_sigint(signo); 1115 } 1116 1117 PUBLIC char * 1118 my_gets(el_mode_t *em, const char *prompt, char *string) 1119 { 1120 static char line[LINE_MAX]; 1121 size_t len; 1122 int cnt; 1123 const char *buf; 1124 HistEvent ev; 1125 1126 sig_check(); 1127 1128 el_prompt = prompt; 1129 if (string) 1130 el_push(em->el, string); 1131 1132 /* 1133 * Let el_gets() deal with flow control. Also, make sure we 1134 * output a ^C when we get a SIGINT as el_gets() doesn't echo 1135 * one. 1136 */ 1137 old_sigint = sig_signal(SIGINT, comp_intr); 1138 buf = el_gets(em->el, &cnt); 1139 (void)sig_signal(SIGINT, old_sigint); 1140 1141 if (buf == NULL) { 1142 sig_check(); 1143 return NULL; 1144 } 1145 1146 if (cnt > 0) { 1147 if (buf[cnt - 1] == '\n') 1148 cnt--; /* trash the trailing LF */ 1149 1150 len = MIN(sizeof(line) - 1, (size_t)cnt); 1151 (void)memcpy(line, buf, len); 1152 } 1153 line[cnt] = '\0'; 1154 1155 /* enter non-empty lines into history */ 1156 if (em->hist) { 1157 const char *p; 1158 1159 p = skip_WSP(line); 1160 if (*p && history(em->hist, &ev, H_ENTER, line) == 0) 1161 (void)printf("Failed history entry: %s", line); 1162 } 1163 sig_check(); 1164 return line; 1165 } 1166 1167 static el_mode_t 1168 init_el_mode( 1169 const char *el_editor, 1170 unsigned char (*completer)(EditLine *, int), 1171 struct name *keys, 1172 int history_size) 1173 { 1174 FILE *nullfp; 1175 el_mode_t em; 1176 1177 (void)memset(&em, 0, sizeof(em)); 1178 1179 if ((nullfp = fopen(_PATH_DEVNULL, "w")) == NULL) 1180 err(EXIT_FAILURE, "Cannot open `%s'", _PATH_DEVNULL); 1181 1182 if ((em.el = el_init(getprogname(), stdin, stdout, nullfp)) == NULL) { 1183 warn("el_init"); 1184 return em; 1185 } 1186 (void)fflush(nullfp); 1187 (void)dup2(STDERR_FILENO, fileno(nullfp)); 1188 1189 (void)el_set(em.el, EL_PROMPT, show_prompt); 1190 (void)el_set(em.el, EL_SIGNAL, 1); /* editline handles the signals. */ 1191 1192 if (el_editor) 1193 (void)el_set(em.el, EL_EDITOR, el_editor); 1194 1195 if (completer) { 1196 struct name *np; 1197 (void)el_set(em.el, EL_ADDFN, "mail-complete", 1198 "Context sensitive argument completion", completer); 1199 for (np = keys; np; np = np->n_flink) 1200 (void)el_set(em.el, EL_BIND, np->n_name, 1201 "mail-complete", NULL); 1202 } 1203 1204 if (history_size) { 1205 HistEvent ev; 1206 if ((em.hist = history_init()) == NULL) { 1207 warn("history_init"); 1208 return em; 1209 } 1210 if (history(em.hist, &ev, H_SETSIZE, history_size) == -1) 1211 (void)printf("history: %s\n", ev.str); 1212 (void)el_set(em.el, EL_HIST, history, em.hist); 1213 } 1214 1215 (void)el_source(em.el, NULL); /* read ~/.editrc */ 1216 1217 return em; 1218 } 1219 1220 1221 struct el_modes_s elm = { 1222 .command = { .el = NULL, .hist = NULL, }, 1223 .string = { .el = NULL, .hist = NULL, }, 1224 .filec = { .el = NULL, .hist = NULL, }, 1225 #ifdef MIME_SUPPORT 1226 .mime_enc = { .el = NULL, .hist = NULL, }, 1227 #endif 1228 }; 1229 1230 PUBLIC void 1231 init_editline(void) 1232 { 1233 const char *mode; 1234 int hist_size; 1235 struct name *keys; 1236 char *cp; 1237 1238 mode = value(ENAME_EL_EDITOR); 1239 1240 cp = value(ENAME_EL_HISTORY_SIZE); 1241 hist_size = cp ? atoi(cp) : 0; 1242 1243 cp = value(ENAME_EL_COMPLETION_KEYS); 1244 keys = cp && *cp ? lexpand(cp, 0) : NULL; 1245 1246 elm.command = init_el_mode(mode, mail_complete, keys, hist_size); 1247 elm.filec = init_el_mode(mode, file_complete, keys, 0); 1248 elm.string = init_el_mode(mode, NULL, NULL, 0); 1249 #ifdef MIME_SUPPORT 1250 elm.mime_enc = init_el_mode(mode, mime_enc_complete, keys, 0); 1251 #endif 1252 return; 1253 } 1254 1255 #endif /* USE_EDITLINE */ 1256