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