1 /* $NetBSD: complete.c,v 1.24 2021/11/10 16:42:47 msaitoh 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.24 2021/11/10 16:42:47 msaitoh 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, *mf; 336 DIR *dd; 337 struct dirent *dp; 338 unsigned char rv; 339 size_t len; 340 341 if ((fname = strrchr(word, '/')) == NULL) { 342 if (word[0] == '+' && (mf = value(ENAME_FOLDER)) != NULL) { 343 if (mf[0] == '/') { 344 (void)estrlcpy(dir, mf, sizeof(dir)); 345 } else { 346 dir[0] = '~'; 347 dir[1] = '/'; 348 (void)estrlcpy(dir + 2, mf, sizeof(dir) - 2); 349 } 350 fname = word + 1; 351 } else { 352 dir[0] = '.'; 353 dir[1] = '\0'; 354 fname = word; 355 } 356 } else { 357 if (fname == word) { 358 dir[0] = '/'; 359 dir[1] = '\0'; 360 } else { 361 len = fname - word + 1; 362 (void)estrlcpy(dir, word, sizeof(dir)); 363 dir[len] = '\0'; 364 } 365 fname++; 366 } 367 if (dir[0] == '~') { 368 char *p; 369 370 if ((p = globulize(dir)) == NULL) 371 return CC_ERROR; 372 (void)estrlcpy(dir, p, sizeof(dir)); 373 free(p); 374 } 375 376 if ((dd = opendir(dir)) == NULL) 377 return CC_ERROR; 378 379 words = mail_sl_init(); 380 len = strlen(fname); 381 382 for (dp = readdir(dd); dp != NULL; dp = readdir(dd)) { 383 if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) 384 continue; 385 386 #if defined(DIRENT_MISSING_D_NAMLEN) 387 if (len > strlen(dp->d_name)) 388 continue; 389 #else 390 if (len > dp->d_namlen) 391 continue; 392 #endif 393 if (strncmp(fname, dp->d_name, len) == 0) { 394 char *tcp; 395 396 tcp = estrdup(dp->d_name); 397 mail_sl_add(words, tcp); 398 } 399 } 400 (void)closedir(dd); 401 402 rv = complete_ambiguous(el, fname, dolist, words); 403 if (rv == CC_REFRESH) { 404 struct stat sb; 405 char path[MAXPATHLEN]; 406 407 (void)estrlcpy(path, dir, sizeof(path)); 408 (void)estrlcat(path, "/", sizeof(path)); 409 (void)estrlcat(path, words->sl_str[0], sizeof(path)); 410 411 if (stat(path, &sb) >= 0) { 412 char suffix[2] = " "; 413 414 if (S_ISDIR(sb.st_mode)) 415 suffix[0] = '/'; 416 if (el_insertstr(el, suffix) == -1) 417 rv = CC_ERROR; 418 } 419 } 420 sl_free(words, 1); 421 return rv; 422 } 423 424 static int 425 find_execs(char *word, char *path, StringList *list) 426 { 427 char *sep; 428 char *dir=path; 429 DIR *dd; 430 struct dirent *dp; 431 size_t len = strlen(word); 432 uid_t uid = getuid(); 433 gid_t gid = getgid(); 434 435 for (sep = dir; sep; dir = sep + 1) { 436 if ((sep=strchr(dir, ':')) != NULL) { 437 *sep=0; 438 } 439 440 if ((dd = opendir(dir)) == NULL) { 441 perror("dir"); 442 return -1; 443 } 444 445 for (dp = readdir(dd); dp != NULL; dp = readdir(dd)) { 446 447 if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) 448 continue; 449 450 #if defined(DIRENT_MISSING_D_NAMLEN) 451 if (len > strlen(dp->d_name)) 452 continue; 453 #else 454 if (len > dp->d_namlen) 455 continue; 456 #endif 457 458 if (strncmp(word, dp->d_name, len) == 0) { 459 struct stat sb; 460 char pathname[ MAXPATHLEN ]; 461 unsigned mask; 462 463 (void)snprintf(pathname, sizeof(pathname), 464 "%s/%s", dir, dp->d_name); 465 if (stat(pathname, &sb) != 0) { 466 perror(pathname); 467 continue; 468 } 469 470 mask = 0001; 471 if (sb.st_uid == uid) mask |= 0100; 472 if (sb.st_gid == gid) mask |= 0010; 473 474 if ((sb.st_mode & mask) != 0) { 475 char *tcp; 476 tcp = estrdup(dp->d_name); 477 mail_sl_add(list, tcp); 478 } 479 } 480 481 } 482 483 (void)closedir(dd); 484 } 485 486 return 0; 487 } 488 489 490 /* 491 * Complete a local executable 492 */ 493 static unsigned char 494 complete_executable(EditLine *el, char *word, int dolist) 495 { 496 StringList *words; 497 char dir[ MAXPATHLEN ]; 498 char *fname; 499 unsigned char rv; 500 size_t len; 501 int error; 502 503 if ((fname = strrchr(word, '/')) == NULL) { 504 dir[0] = '\0'; /* walk the path */ 505 fname = word; 506 } else { 507 if (fname == word) { 508 dir[0] = '/'; 509 dir[1] = '\0'; 510 } else { 511 len = fname - word; 512 (void)strncpy(dir, word, len); 513 dir[fname - word] = '\0'; 514 } 515 fname++; 516 } 517 518 words = sl_init(); 519 520 if (*dir == '\0') { /* walk path */ 521 char *env; 522 char *path; 523 env = getenv("PATH"); 524 len = strlen(env); 525 path = salloc(len + 1); 526 (void)strcpy(path, env); 527 error = find_execs(word, path, words); 528 } 529 else { /* check specified dir only */ 530 error = find_execs(word, dir, words); 531 } 532 if (error != 0) 533 return CC_ERROR; 534 535 rv = complete_ambiguous(el, fname, dolist, words); 536 if (rv == CC_REFRESH) { 537 if (el_insertstr(el, " ") == -1) 538 rv = CC_ERROR; 539 } 540 sl_free(words, 1); 541 542 return rv; 543 } 544 545 546 static unsigned char 547 complete_set(EditLine *el, char *word, int dolist) 548 { 549 struct var *vp; 550 const char **ap; 551 const char **p; 552 int h; 553 int s; 554 size_t len = strlen(word); 555 StringList *words; 556 unsigned char rv; 557 558 words = sl_init(); 559 560 /* allocate space for variables table */ 561 s = 1; 562 for (h = 0; h < HSHSIZE; h++) 563 for (vp = variables[h]; vp != NULL; vp = vp->v_link) 564 s++; 565 ap = salloc(s * sizeof(*ap)); 566 567 /* save the pointers */ 568 for (h = 0, p = ap; h < HSHSIZE; h++) 569 for (vp = variables[h]; vp != NULL; vp = vp->v_link) 570 *p++ = vp->v_name; 571 *p = NULL; 572 sort(ap); 573 for (p = ap; *p != NULL; p++) 574 if (len == 0 || strncmp(*p, word, len) == 0) 575 mail_sl_add(words, estrdup(*p)); 576 577 rv = complete_ambiguous(el, word, dolist, words); 578 579 sl_free(words, 1); 580 581 return rv; 582 } 583 584 585 static unsigned char 586 complete_alias(EditLine *el, char *word, int dolist) 587 { 588 struct grouphead *gh; 589 const char **ap; 590 const char **p; 591 int h; 592 int s; 593 size_t len = strlen(word); 594 StringList *words; 595 unsigned char rv; 596 597 words = sl_init(); 598 599 /* allocate space for alias table */ 600 s = 1; 601 for (h = 0; h < HSHSIZE; h++) 602 for (gh = groups[h]; gh != NULL; gh = gh->g_link) 603 s++; 604 ap = salloc(s * sizeof(*ap)); 605 606 /* save pointers */ 607 p = ap; 608 for (h = 0; h < HSHSIZE; h++) 609 for (gh = groups[h]; gh != NULL; gh = gh->g_link) 610 *p++ = gh->g_name; 611 612 *p = NULL; 613 614 sort(ap); 615 for (p = ap; *p != NULL; p++) 616 if (len == 0 || strncmp(*p, word, len) == 0) 617 mail_sl_add(words, estrdup(*p)); 618 619 rv = complete_ambiguous(el, word, dolist, words); 620 if (rv == CC_REFRESH) { 621 if (el_insertstr(el, " ") == -1) 622 rv = CC_ERROR; 623 } 624 sl_free(words, 1); 625 return rv; 626 } 627 628 629 static unsigned char 630 complete_smopts(EditLine *el, char *word, int dolist) 631 { 632 struct grouphead *gh; 633 struct smopts_s *sp; 634 const char **ap; 635 const char **p; 636 int h; 637 int s1; 638 int s2; 639 size_t len; 640 StringList *words; 641 unsigned char rv; 642 643 len = strlen(word); 644 words = sl_init(); 645 646 /* count the entries in the smoptstbl and groups (alias) tables */ 647 s1 = 1; 648 s2 = 1; 649 for (h = 0; h < HSHSIZE; h++) { 650 for (sp = smoptstbl[h]; sp != NULL; sp = sp->s_link) 651 s1++; 652 for (gh = groups[h]; gh != NULL; gh = gh->g_link) 653 s2++; 654 } 655 656 /* allocate sufficient space for the pointers */ 657 ap = salloc(MAX(s1, s2) * sizeof(*ap)); 658 659 /* 660 * First do the smoptstbl pointers. (case _insensitive_) 661 */ 662 p = ap; 663 for (h = 0; h < HSHSIZE; h++) 664 for (sp = smoptstbl[h]; sp != NULL; sp = sp->s_link) 665 *p++ = sp->s_name; 666 *p = NULL; 667 sort(ap); 668 for (p = ap; *p != NULL; p++) 669 if (len == 0 || strncasecmp(*p, word, len) == 0) 670 mail_sl_add(words, estrdup(*p)); 671 672 /* 673 * Now do the groups (alias) pointers. (case sensitive) 674 */ 675 p = ap; 676 for (h = 0; h < HSHSIZE; h++) 677 for (gh = groups[h]; gh != NULL; gh = gh->g_link) 678 *p++ = gh->g_name; 679 *p = NULL; 680 sort(ap); 681 for (p = ap; *p != NULL; p++) 682 if (len == 0 || strncmp(*p, word, len) == 0) 683 mail_sl_add(words, estrdup(*p)); 684 685 rv = complete_ambiguous(el, word, dolist, words); 686 687 sl_free(words, 1); 688 689 return rv; 690 } 691 692 693 #ifdef THREAD_SUPPORT 694 static unsigned char 695 complete_thread_key(EditLine *el, char *word, int dolist) 696 { 697 const char **ap; 698 const char **p; 699 const char *name; 700 size_t len; 701 StringList *words; 702 unsigned char rv; 703 int cnt; 704 const void *cookie; 705 706 len = strlen(word); 707 words = sl_init(); 708 709 /* count the entries in the table */ 710 /* XXX - have a function return this rather than counting? */ 711 cnt = 1; /* count the NULL terminator */ 712 cookie = NULL; 713 while (thread_next_key_name(&cookie) != NULL) 714 cnt++; 715 716 /* allocate sufficient space for the pointers */ 717 ap = salloc(cnt * sizeof(*ap)); 718 719 /* load the array */ 720 p = ap; 721 cookie = NULL; 722 while ((name = thread_next_key_name(&cookie)) != NULL) 723 *p++ = name; 724 *p = NULL; 725 sort(ap); 726 for (p = ap; *p != NULL; p++) 727 if (len == 0 || strncmp(*p, word, len) == 0) 728 mail_sl_add(words, estrdup(*p)); 729 730 rv = complete_ambiguous(el, word, dolist, words); 731 732 sl_free(words, 1); 733 734 return rv; 735 } 736 #endif /* THREAD_SUPPORT */ 737 738 /* from /usr/src/usr.bin/ftp/main.c(1.101) - end */ 739 /************************************************************************/ 740 741 /* Some people like to bind file completion to CTRL-D. In emacs mode, 742 * CTRL-D is also used to delete the current character, we have to 743 * special case this situation. 744 */ 745 #define EMACS_CTRL_D_BINDING_HACK 746 747 #ifdef EMACS_CTRL_D_BINDING_HACK 748 static int 749 is_emacs_mode(EditLine *el) 750 { 751 char *mode; 752 753 if (el_get(el, EL_EDITOR, &mode) == -1) 754 return 0; 755 return equal(mode, "emacs"); 756 } 757 758 static int 759 emacs_ctrl_d(EditLine *el, const LineInfo *lf, int ch) 760 { 761 static char delunder[3] = { CTRL('f'), CTRL('h'), '\0' }; 762 763 if (ch == CTRL('d') && is_emacs_mode(el)) { /* CTRL-D is special */ 764 if (lf->buffer == lf->lastchar) 765 return CC_EOF; 766 if (lf->cursor != lf->lastchar) { /* delete without using ^D */ 767 el_push(el, delunder); /* ^F^H */ 768 return CC_NORM; 769 } 770 } 771 return -1; 772 } 773 #endif /* EMACS_CTRL_D_BINDING_HACK */ 774 775 /* 776 * Check if this is the second request made for this line indicating 777 * the need to list all the completion possibilities. 778 */ 779 static int 780 get_dolist(const LineInfo *lf) 781 { 782 static char last_line[LINESIZE]; 783 static char *last_cursor_pos; 784 char *cursor_pos; 785 int dolist; 786 size_t len; 787 788 len = lf->lastchar - lf->buffer; 789 if (len >= sizeof(last_line) - 1) 790 return -1; 791 792 cursor_pos = last_line + (lf->cursor - lf->buffer); 793 dolist = 794 cursor_pos == last_cursor_pos && 795 strncmp(last_line, lf->buffer, len) == 0; 796 797 (void)strlcpy(last_line, lf->buffer, len + 1); 798 last_cursor_pos = cursor_pos; 799 800 return dolist; 801 } 802 803 /* 804 * Take the full line (lf) including the command and split it into a 805 * sub-line (returned) and a completion context (cmplarray). 806 */ 807 static LineInfo * 808 split_line(const char **cmplarray, const LineInfo *lf) 809 { 810 static LineInfo li; 811 const struct cmd *c; 812 char *cmdname; 813 char line[LINESIZE]; 814 char *cp; 815 size_t len; 816 817 len = lf->cursor - lf->buffer; 818 if (len + 1 > sizeof(line)) 819 return NULL; 820 821 (void)strlcpy(line, lf->buffer, len + 1); 822 823 li.cursor = line + len; 824 li.lastchar = line + len; 825 826 cp = skip_WSP(line); 827 cmdname = get_cmdname(cp); 828 cp += strlen(cmdname); 829 830 if (cp == li.cursor) { 831 *cmplarray = "c"; 832 li.buffer = cmdname; 833 return &li; 834 } 835 836 c = lex(cmdname); 837 if (c == NULL) 838 return NULL; 839 840 *cmplarray = c->c_complete; 841 if (c->c_pipe) { 842 char *cp2; 843 if ((cp2 = shellpr(cp)) != NULL) { 844 cp = cp2; 845 # define XX(a) ((a) + ((a)[1] == '>' ? 2 : 1)) 846 while ((cp2 = shellpr(XX(cp))) != NULL) 847 cp = cp2; 848 849 if (*cp == '|') { 850 *cmplarray = "xF"; 851 cp = skip_WSP(cp + 1); 852 } 853 else { 854 assert(*cp == '>'); 855 cp = skip_WSP(XX(cp)); 856 *cmplarray = "f"; 857 } 858 # undef XX 859 } 860 } 861 li.buffer = cp; 862 return &li; 863 } 864 865 /* 866 * Split a sub-line and a completion context into a word and a 867 * completion type. Use the editline tokenizer to handle the quoting 868 * and splitting. 869 */ 870 static char * 871 split_word(int *cmpltype, const char *cmplarray, LineInfo *li) 872 { 873 static Tokenizer *t = NULL; 874 const char **argv; 875 char *word; 876 int argc; 877 int cursorc; 878 int cursoro; 879 int arraylen; 880 881 if (t != NULL) 882 tok_reset(t); 883 else { 884 if ((t = tok_init(NULL)) == NULL) 885 err(EXIT_FAILURE, "tok_init"); 886 } 887 if (tok_line(t, li, &argc, &argv, &cursorc, &cursoro) == -1) 888 err(EXIT_FAILURE, "tok_line"); 889 890 if (cursorc >= argc) 891 word = __UNCONST(""); 892 else { 893 word = salloc((size_t)cursoro + 1); 894 (void)strlcpy(word, argv[cursorc], (size_t)cursoro + 1); 895 } 896 897 /* check for 'continuation' completes (which are uppercase) */ 898 arraylen = (int)strlen(cmplarray); 899 if (cursorc >= arraylen && 900 arraylen > 0 && 901 isupper((unsigned char)cmplarray[arraylen - 1])) 902 cursorc = arraylen - 1; 903 904 if (cursorc >= arraylen) 905 return NULL; 906 907 *cmpltype = cmplarray[cursorc]; 908 return word; 909 } 910 911 /* 912 * A generic complete routine for the mail command line. 913 */ 914 static unsigned char 915 mail_complete(EditLine *el, int ch) 916 { 917 LineInfo *li; 918 const LineInfo *lf; 919 const char *cmplarray; 920 int dolist; 921 int cmpltype; 922 char *word; 923 924 lf = el_line(el); 925 926 #ifdef EMACS_CTRL_D_BINDING_HACK 927 { 928 int cc_ret; 929 if ((cc_ret = emacs_ctrl_d(el, lf, ch)) != -1) 930 return cc_ret; 931 } 932 #endif /* EMACS_CTRL_D_BINDING_HACK */ 933 934 if ((dolist = get_dolist(lf)) == -1) 935 return CC_ERROR; 936 937 if ((li = split_line(&cmplarray, lf)) == NULL) 938 return CC_ERROR; 939 940 if ((word = split_word(&cmpltype, cmplarray, li)) == NULL) 941 return CC_ERROR; 942 943 switch (cmpltype) { 944 case 'a': /* alias complete */ 945 case 'A': 946 return complete_alias(el, word, dolist); 947 948 case 'c': /* command complete */ 949 case 'C': 950 return complete_command(el, word, dolist); 951 952 case 'f': /* filename complete */ 953 case 'F': 954 return complete_filename(el, word, dolist); 955 956 case 'm': 957 case 'M': 958 return complete_smopts(el, word, dolist); 959 960 case 'n': /* no complete */ 961 case 'N': /* no complete */ 962 return CC_ERROR; 963 964 case 's': 965 case 'S': 966 return complete_set(el, word, dolist); 967 #ifdef THREAD_SUPPORT 968 case 't': 969 case 'T': 970 return complete_thread_key(el, word, dolist); 971 #endif 972 case 'x': /* executable complete */ 973 case 'X': 974 return complete_executable(el, word, dolist); 975 976 default: 977 warnx("unknown complete type `%c'", cmpltype); 978 #if 0 979 assert(/*CONSTCOND*/0); 980 #endif 981 return CC_ERROR; 982 } 983 /* NOTREACHED */ 984 } 985 986 987 /* 988 * A generic file completion routine. 989 */ 990 static unsigned char 991 file_complete(EditLine *el, int ch) 992 { 993 static char word[LINESIZE]; 994 const LineInfo *lf; 995 size_t word_len; 996 int dolist; 997 998 lf = el_line(el); 999 1000 #ifdef EMACS_CTRL_D_BINDING_HACK 1001 { 1002 int cc_ret; 1003 if ((cc_ret = emacs_ctrl_d(el, lf, ch)) != -1) 1004 return cc_ret; 1005 } 1006 #endif /* EMACS_CTRL_D_BINDING_HACK */ 1007 1008 word_len = lf->cursor - lf->buffer; 1009 if (word_len + 1 > sizeof(word)) 1010 return CC_ERROR; 1011 1012 (void)strlcpy(word, lf->buffer, word_len + 1); /* do not use estrlcpy here! */ 1013 1014 if ((dolist = get_dolist(lf)) == -1) 1015 return CC_ERROR; 1016 1017 return complete_filename(el, word, dolist); 1018 } 1019 1020 1021 #ifdef MIME_SUPPORT 1022 /* 1023 * Complete mime_transfer_encoding type. 1024 */ 1025 static unsigned char 1026 mime_enc_complete(EditLine *el, int ch) 1027 { 1028 static char word[LINESIZE]; 1029 StringList *words; 1030 unsigned char rv; 1031 const LineInfo *lf; 1032 size_t word_len; 1033 int dolist; 1034 1035 lf = el_line(el); 1036 1037 #ifdef EMACS_CTRL_D_BINDING_HACK 1038 { 1039 int cc_ret; 1040 if ((cc_ret = emacs_ctrl_d(el, lf, ch)) != -1) 1041 return cc_ret; 1042 } 1043 #endif /* EMACS_CTRL_D_BINDING_HACK */ 1044 1045 word_len = lf->cursor - lf->buffer; 1046 if (word_len >= sizeof(word) - 1) 1047 return CC_ERROR; 1048 1049 words = mail_sl_init(); 1050 { 1051 const char *ename; 1052 const void *cookie; 1053 cookie = NULL; 1054 for (ename = mime_next_encoding_name(&cookie); 1055 ename; 1056 ename = mime_next_encoding_name(&cookie)) 1057 if (word_len == 0 || 1058 strncmp(lf->buffer, ename, word_len) == 0) { 1059 char *cp; 1060 cp = estrdup(ename); 1061 mail_sl_add(words, cp); 1062 } 1063 } 1064 (void)strlcpy(word, lf->buffer, word_len + 1); 1065 1066 if ((dolist = get_dolist(lf)) == -1) 1067 return CC_ERROR; 1068 1069 rv = complete_ambiguous(el, word, dolist, words); 1070 1071 sl_free(words, 1); 1072 return rv; 1073 } 1074 #endif /* MIME_SUPPORT */ 1075 1076 1077 /************************************************************************* 1078 * Our public interface to el_gets(): 1079 * 1080 * init_editline() 1081 * Initializes of all editline and completion data structures. 1082 * 1083 * my_gets() 1084 * Displays prompt, calls el_gets() and deals with history. 1085 * Returns the next line of input as a NULL termnated string 1086 * without the trailing newline, or NULL if el_gets() sees is an 1087 * error or signal. 1088 */ 1089 1090 static const char *el_prompt; 1091 1092 /*ARGSUSED*/ 1093 static const char * 1094 show_prompt(EditLine *e __unused) 1095 { 1096 return el_prompt; 1097 } 1098 1099 /* 1100 * Write the current INTR character to fp in a friendly form. 1101 */ 1102 static void 1103 echo_INTR(void *p) 1104 { 1105 struct termios ttybuf; 1106 char buf[5]; 1107 FILE *fp; 1108 1109 fp = p; 1110 if (tcgetattr(fileno(stdin), &ttybuf) == -1) 1111 warn("tcgetattr"); 1112 else { 1113 (void)vis(buf, ttybuf.c_cc[VINTR], VIS_SAFE | VIS_NOSLASH, 0); 1114 (void)fprintf(fp, "%s", buf); 1115 (void)fflush(fp); 1116 } 1117 } 1118 1119 static sig_t old_sigint; 1120 static void 1121 comp_intr(int signo) 1122 { 1123 1124 echo_INTR(stdout); 1125 old_sigint(signo); 1126 } 1127 1128 PUBLIC char * 1129 my_gets(el_mode_t *em, const char *prompt, char *string) 1130 { 1131 static char line[LINE_MAX]; 1132 size_t len; 1133 int cnt; 1134 const char *buf; 1135 HistEvent ev; 1136 1137 sig_check(); 1138 1139 el_prompt = prompt; 1140 if (string) 1141 el_push(em->el, string); 1142 1143 /* 1144 * Let el_gets() deal with flow control. Also, make sure we 1145 * output a ^C when we get a SIGINT as el_gets() doesn't echo 1146 * one. 1147 */ 1148 old_sigint = sig_signal(SIGINT, comp_intr); 1149 buf = el_gets(em->el, &cnt); 1150 (void)sig_signal(SIGINT, old_sigint); 1151 1152 if (buf == NULL) { 1153 sig_check(); 1154 return NULL; 1155 } 1156 1157 if (cnt > 0) { 1158 if (buf[cnt - 1] == '\n') 1159 cnt--; /* trash the trailing LF */ 1160 1161 len = MIN(sizeof(line) - 1, (size_t)cnt); 1162 (void)memcpy(line, buf, len); 1163 } 1164 line[cnt] = '\0'; 1165 1166 /* enter non-empty lines into history */ 1167 if (em->hist) { 1168 const char *p; 1169 1170 p = skip_WSP(line); 1171 if (*p && history(em->hist, &ev, H_ENTER, line) == 0) 1172 (void)printf("Failed history entry: %s", line); 1173 } 1174 sig_check(); 1175 return line; 1176 } 1177 1178 static el_mode_t 1179 init_el_mode( 1180 const char *el_editor, 1181 unsigned char (*completer)(EditLine *, int), 1182 struct name *keys, 1183 int history_size) 1184 { 1185 FILE *nullfp; 1186 el_mode_t em; 1187 1188 (void)memset(&em, 0, sizeof(em)); 1189 1190 if ((nullfp = fopen(_PATH_DEVNULL, "w")) == NULL) 1191 err(EXIT_FAILURE, "Cannot open `%s'", _PATH_DEVNULL); 1192 1193 if ((em.el = el_init(getprogname(), stdin, stdout, nullfp)) == NULL) { 1194 warn("el_init"); 1195 return em; 1196 } 1197 (void)fflush(nullfp); 1198 (void)dup2(STDERR_FILENO, fileno(nullfp)); 1199 1200 (void)el_set(em.el, EL_PROMPT, show_prompt); 1201 (void)el_set(em.el, EL_SIGNAL, 1); /* editline handles the signals. */ 1202 1203 if (el_editor) 1204 (void)el_set(em.el, EL_EDITOR, el_editor); 1205 1206 if (completer) { 1207 struct name *np; 1208 (void)el_set(em.el, EL_ADDFN, "mail-complete", 1209 "Context sensitive argument completion", completer); 1210 for (np = keys; np; np = np->n_flink) 1211 (void)el_set(em.el, EL_BIND, np->n_name, 1212 "mail-complete", NULL); 1213 } 1214 1215 if (history_size) { 1216 HistEvent ev; 1217 if ((em.hist = history_init()) == NULL) { 1218 warn("history_init"); 1219 return em; 1220 } 1221 if (history(em.hist, &ev, H_SETSIZE, history_size) == -1) 1222 (void)printf("history: %s\n", ev.str); 1223 (void)el_set(em.el, EL_HIST, history, em.hist); 1224 } 1225 1226 (void)el_source(em.el, NULL); /* read ~/.editrc */ 1227 1228 return em; 1229 } 1230 1231 1232 struct el_modes_s elm = { 1233 .command = { .el = NULL, .hist = NULL, }, 1234 .string = { .el = NULL, .hist = NULL, }, 1235 .filec = { .el = NULL, .hist = NULL, }, 1236 #ifdef MIME_SUPPORT 1237 .mime_enc = { .el = NULL, .hist = NULL, }, 1238 #endif 1239 }; 1240 1241 PUBLIC void 1242 init_editline(void) 1243 { 1244 const char *mode; 1245 int hist_size; 1246 struct name *keys; 1247 char *cp; 1248 1249 mode = value(ENAME_EL_EDITOR); 1250 1251 cp = value(ENAME_EL_HISTORY_SIZE); 1252 hist_size = cp ? atoi(cp) : 0; 1253 1254 cp = value(ENAME_EL_COMPLETION_KEYS); 1255 keys = cp && *cp ? lexpand(cp, 0) : NULL; 1256 1257 elm.command = init_el_mode(mode, mail_complete, keys, hist_size); 1258 elm.filec = init_el_mode(mode, file_complete, keys, 0); 1259 elm.string = init_el_mode(mode, NULL, NULL, 0); 1260 #ifdef MIME_SUPPORT 1261 elm.mime_enc = init_el_mode(mode, mime_enc_complete, keys, 0); 1262 #endif 1263 return; 1264 } 1265 1266 #endif /* USE_EDITLINE */ 1267