1 /*- 2 * Copyright (c) 1993 3 * The Regents of the University of California. All rights reserved. 4 * 5 * This code is derived from software contributed to Berkeley by 6 * Kenneth Almquist. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 3. Neither the name of the University nor the names of its contributors 17 * may be used to endorse or promote products derived from this software 18 * without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 21 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 24 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 * SUCH DAMAGE. 31 */ 32 33 #ifndef lint 34 #if 0 35 static char sccsid[] = "@(#)histedit.c 8.2 (Berkeley) 5/4/95"; 36 #endif 37 #endif /* not lint */ 38 #include <sys/cdefs.h> 39 __FBSDID("$FreeBSD$"); 40 41 #include <sys/param.h> 42 #include <sys/stat.h> 43 #include <dirent.h> 44 #include <errno.h> 45 #include <fcntl.h> 46 #include <limits.h> 47 #include <paths.h> 48 #include <stdbool.h> 49 #include <stdio.h> 50 #include <stdlib.h> 51 #include <unistd.h> 52 /* 53 * Editline and history functions (and glue). 54 */ 55 #include "shell.h" 56 #include "parser.h" 57 #include "var.h" 58 #include "options.h" 59 #include "main.h" 60 #include "output.h" 61 #include "mystring.h" 62 #include "builtins.h" 63 #ifndef NO_HISTORY 64 #include "myhistedit.h" 65 #include "error.h" 66 #include "eval.h" 67 #include "memalloc.h" 68 69 #define MAXHISTLOOPS 4 /* max recursions through fc */ 70 #define DEFEDITOR "ed" /* default editor *should* be $EDITOR */ 71 72 History *hist; /* history cookie */ 73 EditLine *el; /* editline cookie */ 74 int displayhist; 75 static int savehist; 76 static FILE *el_in, *el_out; 77 static bool in_command_completion; 78 79 static char *fc_replace(const char *, char *, char *); 80 static int not_fcnumber(const char *); 81 static int str_to_event(const char *, int); 82 static int comparator(const void *, const void *, void *); 83 static char **sh_matches(const char *, int, int); 84 static const char *append_char_function(const char *); 85 static unsigned char sh_complete(EditLine *, int); 86 87 static const char * 88 get_histfile(void) 89 { 90 const char *histfile; 91 92 /* don't try to save if the history size is 0 */ 93 if (hist == NULL || !strcmp(histsizeval(), "0")) 94 return (NULL); 95 histfile = expandstr("${HISTFILE-${HOME-}/.sh_history}"); 96 97 if (histfile[0] == '\0') 98 return (NULL); 99 return (histfile); 100 } 101 102 void 103 histsave(void) 104 { 105 HistEvent he; 106 char *histtmpname = NULL; 107 const char *histfile; 108 int fd; 109 FILE *f; 110 111 if (!savehist || (histfile = get_histfile()) == NULL) 112 return; 113 INTOFF; 114 asprintf(&histtmpname, "%s.XXXXXXXXXX", histfile); 115 if (histtmpname == NULL) { 116 INTON; 117 return; 118 } 119 fd = mkstemp(histtmpname); 120 if (fd == -1 || (f = fdopen(fd, "w")) == NULL) { 121 free(histtmpname); 122 INTON; 123 return; 124 } 125 if (history(hist, &he, H_SAVE_FP, f) < 1 || 126 rename(histtmpname, histfile) == -1) 127 unlink(histtmpname); 128 fclose(f); 129 free(histtmpname); 130 INTON; 131 132 } 133 134 void 135 histload(void) 136 { 137 const char *histfile; 138 HistEvent he; 139 140 if ((histfile = get_histfile()) == NULL) 141 return; 142 errno = 0; 143 if (history(hist, &he, H_LOAD, histfile) != -1 || errno == ENOENT) 144 savehist = 1; 145 } 146 147 /* 148 * Set history and editing status. Called whenever the status may 149 * have changed (figures out what to do). 150 */ 151 void 152 histedit(void) 153 { 154 155 #define editing (Eflag || Vflag) 156 157 if (iflag) { 158 if (!hist) { 159 /* 160 * turn history on 161 */ 162 INTOFF; 163 hist = history_init(); 164 INTON; 165 166 if (hist != NULL) 167 sethistsize(histsizeval()); 168 else 169 out2fmt_flush("sh: can't initialize history\n"); 170 } 171 if (editing && !el && isatty(0)) { /* && isatty(2) ??? */ 172 /* 173 * turn editing on 174 */ 175 char *term; 176 177 INTOFF; 178 if (el_in == NULL) 179 el_in = fdopen(0, "r"); 180 if (el_out == NULL) 181 el_out = fdopen(2, "w"); 182 if (el_in == NULL || el_out == NULL) 183 goto bad; 184 term = lookupvar("TERM"); 185 if (term) 186 setenv("TERM", term, 1); 187 else 188 unsetenv("TERM"); 189 el = el_init(arg0, el_in, el_out, el_out); 190 if (el != NULL) { 191 if (hist) 192 el_set(el, EL_HIST, history, hist); 193 el_set(el, EL_PROMPT_ESC, getprompt, '\001'); 194 el_set(el, EL_ADDFN, "sh-complete", 195 "Filename completion", 196 sh_complete); 197 } else { 198 bad: 199 out2fmt_flush("sh: can't initialize editing\n"); 200 } 201 INTON; 202 } else if (!editing && el) { 203 INTOFF; 204 el_end(el); 205 el = NULL; 206 INTON; 207 } 208 if (el) { 209 if (Vflag) 210 el_set(el, EL_EDITOR, "vi"); 211 else if (Eflag) { 212 el_set(el, EL_EDITOR, "emacs"); 213 } 214 el_set(el, EL_BIND, "^I", "sh-complete", NULL); 215 el_source(el, NULL); 216 } 217 } else { 218 INTOFF; 219 if (el) { /* no editing if not interactive */ 220 el_end(el); 221 el = NULL; 222 } 223 if (hist) { 224 history_end(hist); 225 hist = NULL; 226 } 227 INTON; 228 } 229 } 230 231 232 void 233 sethistsize(const char *hs) 234 { 235 int histsize; 236 HistEvent he; 237 238 if (hist != NULL) { 239 if (hs == NULL || !is_number(hs)) 240 histsize = 100; 241 else 242 histsize = atoi(hs); 243 history(hist, &he, H_SETSIZE, histsize); 244 history(hist, &he, H_SETUNIQUE, 1); 245 } 246 } 247 248 void 249 setterm(const char *term) 250 { 251 if (rootshell && el != NULL && term != NULL) 252 el_set(el, EL_TERMINAL, term); 253 } 254 255 int 256 histcmd(int argc, char **argv __unused) 257 { 258 const char *editor = NULL; 259 HistEvent he; 260 int lflg = 0, nflg = 0, rflg = 0, sflg = 0; 261 int i, retval; 262 const char *firststr, *laststr; 263 int first, last, direction; 264 char *pat = NULL, *repl = NULL; 265 static int active = 0; 266 struct jmploc jmploc; 267 struct jmploc *savehandler; 268 char editfilestr[PATH_MAX]; 269 char *volatile editfile; 270 FILE *efp = NULL; 271 int oldhistnum; 272 273 if (hist == NULL) 274 error("history not active"); 275 276 if (argc == 1) 277 error("missing history argument"); 278 279 while (not_fcnumber(*argptr)) 280 do { 281 switch (nextopt("e:lnrs")) { 282 case 'e': 283 editor = shoptarg; 284 break; 285 case 'l': 286 lflg = 1; 287 break; 288 case 'n': 289 nflg = 1; 290 break; 291 case 'r': 292 rflg = 1; 293 break; 294 case 's': 295 sflg = 1; 296 break; 297 case '\0': 298 goto operands; 299 } 300 } while (nextopt_optptr != NULL); 301 operands: 302 savehandler = handler; 303 /* 304 * If executing... 305 */ 306 if (lflg == 0 || editor || sflg) { 307 lflg = 0; /* ignore */ 308 editfile = NULL; 309 /* 310 * Catch interrupts to reset active counter and 311 * cleanup temp files. 312 */ 313 if (setjmp(jmploc.loc)) { 314 active = 0; 315 if (editfile) 316 unlink(editfile); 317 handler = savehandler; 318 longjmp(handler->loc, 1); 319 } 320 handler = &jmploc; 321 if (++active > MAXHISTLOOPS) { 322 active = 0; 323 displayhist = 0; 324 error("called recursively too many times"); 325 } 326 /* 327 * Set editor. 328 */ 329 if (sflg == 0) { 330 if (editor == NULL && 331 (editor = bltinlookup("FCEDIT", 1)) == NULL && 332 (editor = bltinlookup("EDITOR", 1)) == NULL) 333 editor = DEFEDITOR; 334 if (editor[0] == '-' && editor[1] == '\0') { 335 sflg = 1; /* no edit */ 336 editor = NULL; 337 } 338 } 339 } 340 341 /* 342 * If executing, parse [old=new] now 343 */ 344 if (lflg == 0 && *argptr != NULL && 345 ((repl = strchr(*argptr, '=')) != NULL)) { 346 pat = *argptr; 347 *repl++ = '\0'; 348 argptr++; 349 } 350 /* 351 * determine [first] and [last] 352 */ 353 if (*argptr == NULL) { 354 firststr = lflg ? "-16" : "-1"; 355 laststr = "-1"; 356 } else if (argptr[1] == NULL) { 357 firststr = argptr[0]; 358 laststr = lflg ? "-1" : argptr[0]; 359 } else if (argptr[2] == NULL) { 360 firststr = argptr[0]; 361 laststr = argptr[1]; 362 } else 363 error("too many arguments"); 364 /* 365 * Turn into event numbers. 366 */ 367 first = str_to_event(firststr, 0); 368 last = str_to_event(laststr, 1); 369 370 if (rflg) { 371 i = last; 372 last = first; 373 first = i; 374 } 375 /* 376 * XXX - this should not depend on the event numbers 377 * always increasing. Add sequence numbers or offset 378 * to the history element in next (diskbased) release. 379 */ 380 direction = first < last ? H_PREV : H_NEXT; 381 382 /* 383 * If editing, grab a temp file. 384 */ 385 if (editor) { 386 int fd; 387 INTOFF; /* easier */ 388 sprintf(editfilestr, "%s/_shXXXXXX", _PATH_TMP); 389 if ((fd = mkstemp(editfilestr)) < 0) 390 error("can't create temporary file %s", editfile); 391 editfile = editfilestr; 392 if ((efp = fdopen(fd, "w")) == NULL) { 393 close(fd); 394 error("Out of space"); 395 } 396 } 397 398 /* 399 * Loop through selected history events. If listing or executing, 400 * do it now. Otherwise, put into temp file and call the editor 401 * after. 402 * 403 * The history interface needs rethinking, as the following 404 * convolutions will demonstrate. 405 */ 406 history(hist, &he, H_FIRST); 407 retval = history(hist, &he, H_NEXT_EVENT, first); 408 for (;retval != -1; retval = history(hist, &he, direction)) { 409 if (lflg) { 410 if (!nflg) 411 out1fmt("%5d ", he.num); 412 out1str(he.str); 413 } else { 414 const char *s = pat ? 415 fc_replace(he.str, pat, repl) : he.str; 416 417 if (sflg) { 418 if (displayhist) { 419 out2str(s); 420 flushout(out2); 421 } 422 evalstring(s, 0); 423 if (displayhist && hist) { 424 /* 425 * XXX what about recursive and 426 * relative histnums. 427 */ 428 oldhistnum = he.num; 429 history(hist, &he, H_ENTER, s); 430 /* 431 * XXX H_ENTER moves the internal 432 * cursor, set it back to the current 433 * entry. 434 */ 435 history(hist, &he, 436 H_NEXT_EVENT, oldhistnum); 437 } 438 } else 439 fputs(s, efp); 440 } 441 /* 442 * At end? (if we were to lose last, we'd sure be 443 * messed up). 444 */ 445 if (he.num == last) 446 break; 447 } 448 if (editor) { 449 char *editcmd; 450 451 fclose(efp); 452 INTON; 453 editcmd = stalloc(strlen(editor) + strlen(editfile) + 2); 454 sprintf(editcmd, "%s %s", editor, editfile); 455 evalstring(editcmd, 0); /* XXX - should use no JC command */ 456 readcmdfile(editfile, 0 /* verify */); /* XXX - should read back - quick tst */ 457 unlink(editfile); 458 } 459 460 if (lflg == 0 && active > 0) 461 --active; 462 if (displayhist) 463 displayhist = 0; 464 handler = savehandler; 465 return 0; 466 } 467 468 static char * 469 fc_replace(const char *s, char *p, char *r) 470 { 471 char *dest; 472 int plen = strlen(p); 473 474 STARTSTACKSTR(dest); 475 while (*s) { 476 if (*s == *p && strncmp(s, p, plen) == 0) { 477 STPUTS(r, dest); 478 s += plen; 479 *p = '\0'; /* so no more matches */ 480 } else 481 STPUTC(*s++, dest); 482 } 483 STPUTC('\0', dest); 484 dest = grabstackstr(dest); 485 486 return (dest); 487 } 488 489 static int 490 not_fcnumber(const char *s) 491 { 492 if (s == NULL) 493 return (0); 494 if (*s == '-') 495 s++; 496 return (!is_number(s)); 497 } 498 499 static int 500 str_to_event(const char *str, int last) 501 { 502 HistEvent he; 503 const char *s = str; 504 int relative = 0; 505 int i, retval; 506 507 retval = history(hist, &he, H_FIRST); 508 switch (*s) { 509 case '-': 510 relative = 1; 511 /*FALLTHROUGH*/ 512 case '+': 513 s++; 514 } 515 if (is_number(s)) { 516 i = atoi(s); 517 if (relative) { 518 while (retval != -1 && i--) { 519 retval = history(hist, &he, H_NEXT); 520 } 521 if (retval == -1) 522 retval = history(hist, &he, H_LAST); 523 } else { 524 retval = history(hist, &he, H_NEXT_EVENT, i); 525 if (retval == -1) { 526 /* 527 * the notion of first and last is 528 * backwards to that of the history package 529 */ 530 retval = history(hist, &he, last ? H_FIRST : H_LAST); 531 } 532 } 533 if (retval == -1) 534 error("history number %s not found (internal error)", 535 str); 536 } else { 537 /* 538 * pattern 539 */ 540 retval = history(hist, &he, H_PREV_STR, str); 541 if (retval == -1) 542 error("history pattern not found: %s", str); 543 } 544 return (he.num); 545 } 546 547 int 548 bindcmd(int argc, char **argv) 549 { 550 int ret; 551 FILE *old; 552 FILE *out; 553 554 if (el == NULL) 555 error("line editing is disabled"); 556 557 INTOFF; 558 559 out = out1fp(); 560 if (out == NULL) 561 error("Out of space"); 562 563 el_get(el, EL_GETFP, 1, &old); 564 el_set(el, EL_SETFP, 1, out); 565 566 ret = el_parse(el, argc, __DECONST(const char **, argv)); 567 568 el_set(el, EL_SETFP, 1, old); 569 570 fclose(out); 571 572 if (argc > 1 && argv[1][0] == '-' && 573 memchr("ve", argv[1][1], 2) != NULL) { 574 Vflag = argv[1][1] == 'v'; 575 Eflag = !Vflag; 576 histedit(); 577 } 578 579 INTON; 580 581 return ret; 582 } 583 584 /* 585 * Comparator function for qsort(). The use of curpos here is to skip 586 * characters that we already know to compare equal (common prefix). 587 */ 588 static int 589 comparator(const void *a, const void *b, void *thunk) 590 { 591 size_t curpos = (intptr_t)thunk; 592 593 return (strcmp(*(char *const *)a + curpos, 594 *(char *const *)b + curpos)); 595 } 596 597 /* 598 * This function is passed to libedit's fn_complete2(). The library will use 599 * it instead of its standard function that finds matching files in current 600 * directory. If we're at the start of the line, we want to look for 601 * available commands from all paths in $PATH. 602 */ 603 static char 604 **sh_matches(const char *text, int start, int end) 605 { 606 char *free_path = NULL, *path; 607 const char *dirname; 608 char **matches = NULL; 609 size_t i = 0, size = 16, uniq; 610 size_t curpos = end - start, lcstring = -1; 611 612 in_command_completion = false; 613 if (start > 0 || memchr("/.~", text[0], 3) != NULL) 614 return (NULL); 615 in_command_completion = true; 616 if ((free_path = path = strdup(pathval())) == NULL) 617 goto out; 618 if ((matches = malloc(size * sizeof(matches[0]))) == NULL) 619 goto out; 620 while ((dirname = strsep(&path, ":")) != NULL) { 621 struct dirent *entry; 622 DIR *dir; 623 int dfd; 624 625 dir = opendir(dirname[0] == '\0' ? "." : dirname); 626 if (dir == NULL) 627 continue; 628 if ((dfd = dirfd(dir)) == -1) { 629 closedir(dir); 630 continue; 631 } 632 while ((entry = readdir(dir)) != NULL) { 633 struct stat statb; 634 char **rmatches; 635 636 if (strncmp(entry->d_name, text, curpos) != 0) 637 continue; 638 if (entry->d_type == DT_UNKNOWN || entry->d_type == DT_LNK) { 639 if (fstatat(dfd, entry->d_name, &statb, 0) == -1) 640 continue; 641 if (!S_ISREG(statb.st_mode)) 642 continue; 643 } else if (entry->d_type != DT_REG) 644 continue; 645 matches[++i] = strdup(entry->d_name); 646 if (i < size - 1) 647 continue; 648 size *= 2; 649 rmatches = reallocarray(matches, size, sizeof(matches[0])); 650 if (rmatches == NULL) { 651 closedir(dir); 652 goto out; 653 } 654 matches = rmatches; 655 } 656 closedir(dir); 657 } 658 out: 659 free(free_path); 660 if (i == 0) { 661 free(matches); 662 return (NULL); 663 } 664 uniq = 1; 665 if (i > 1) { 666 qsort_s(matches + 1, i, sizeof(matches[0]), comparator, 667 (void *)(intptr_t)curpos); 668 for (size_t k = 2; k <= i; k++) { 669 const char *l = matches[uniq] + curpos; 670 const char *r = matches[k] + curpos; 671 size_t common = 0; 672 673 while (*l != '\0' && *r != '\0' && *l == *r) 674 (void)l++, r++, common++; 675 if (common < lcstring) 676 lcstring = common; 677 if (*l == *r) 678 free(matches[k]); 679 else 680 matches[++uniq] = matches[k]; 681 } 682 } 683 matches[uniq + 1] = NULL; 684 /* 685 * matches[0] is special: it's not a real matching file name but 686 * a common prefix for all matching names. It can't be null, unlike 687 * any other element of the array. When strings matches[0] and 688 * matches[1] compare equal and matches[2] is null that means to 689 * libedit that there is only a single match. It will then replace 690 * user input with possibly escaped string in matches[0] which is the 691 * reason to copy the full name of the only match. 692 */ 693 if (uniq == 1) 694 matches[0] = strdup(matches[1]); 695 else if (lcstring != (size_t)-1) 696 matches[0] = strndup(matches[1], curpos + lcstring); 697 else 698 matches[0] = strdup(text); 699 if (matches[0] == NULL) { 700 for (size_t k = 1; k <= uniq; k++) 701 free(matches[k]); 702 free(matches); 703 return (NULL); 704 } 705 return (matches); 706 } 707 708 /* 709 * If we don't specify this function as app_func in the call to fn_complete2, 710 * libedit will use the default one, which adds a " " to plain files and 711 * a "/" to directories regardless of whether it's a command name or a plain 712 * path (relative or absolute). We never want to add "/" to commands. 713 * 714 * For example, after I did "mkdir rmdir", "rmdi" would be autocompleted to 715 * "rmdir/" instead of "rmdir ". 716 */ 717 static const char * 718 append_char_function(const char *name) 719 { 720 struct stat stbuf; 721 char *expname = name[0] == '~' ? fn_tilde_expand(name) : NULL; 722 const char *rs; 723 724 if (!in_command_completion && 725 stat(expname ? expname : name, &stbuf) == 0 && 726 S_ISDIR(stbuf.st_mode)) 727 rs = "/"; 728 else 729 rs = " "; 730 free(expname); 731 return (rs); 732 } 733 734 /* 735 * This is passed to el_set(el, EL_ADDFN, ...) so that it's possible to 736 * bind a key (tab by default) to execute the function. 737 */ 738 unsigned char 739 sh_complete(EditLine *sel, int ch __unused) 740 { 741 return (unsigned char)fn_complete2(sel, NULL, sh_matches, 742 L" \t\n\"\\'`@$><=;|&{(", NULL, append_char_function, 743 (size_t)100, NULL, &((int) {0}), NULL, NULL, FN_QUOTE_MATCH); 744 } 745 746 #else 747 #include "error.h" 748 749 int 750 histcmd(int argc __unused, char **argv __unused) 751 { 752 753 error("not compiled with history support"); 754 /*NOTREACHED*/ 755 return (0); 756 } 757 758 int 759 bindcmd(int argc __unused, char **argv __unused) 760 { 761 762 error("not compiled with line editing support"); 763 return (0); 764 } 765 #endif 766