1 /* $OpenBSD: history.c,v 1.84 2019/10/27 15:02:19 jca Exp $ */ 2 3 /* 4 * command history 5 */ 6 7 /* 8 * This file contains 9 * a) the original in-memory history mechanism 10 * b) a more complicated mechanism done by pc@hillside.co.uk 11 * that more closely follows the real ksh way of doing 12 * things. 13 */ 14 15 #include <sys/stat.h> 16 17 #include <errno.h> 18 #include <fcntl.h> 19 #include <stdlib.h> 20 #include <stdio.h> 21 #include <string.h> 22 #include <unistd.h> 23 #include <vis.h> 24 25 #include "sh.h" 26 27 static void history_write(void); 28 static FILE *history_open(void); 29 static void history_load(Source *); 30 static void history_close(void); 31 32 static int hist_execute(char *); 33 static int hist_replace(char **, const char *, const char *, int); 34 static char **hist_get(const char *, int, int); 35 static char **hist_get_oldest(void); 36 static void histbackup(void); 37 38 static FILE *histfh; 39 static char **histbase; /* actual start of the history[] allocation */ 40 static char **current; /* current position in history[] */ 41 static char *hname; /* current name of history file */ 42 static int hstarted; /* set after hist_init() called */ 43 static int ignoredups; /* ditch duplicated history lines? */ 44 static int ignorespace; /* ditch lines starting with a space? */ 45 static Source *hist_source; 46 static uint32_t line_co; 47 48 static struct stat last_sb; 49 50 static volatile sig_atomic_t c_fc_depth; 51 52 int 53 c_fc(char **wp) 54 { 55 struct shf *shf; 56 struct temp *tf = NULL; 57 char *p, *editor = NULL; 58 int gflag = 0, lflag = 0, nflag = 0, sflag = 0, rflag = 0; 59 int optc, ret; 60 char *first = NULL, *last = NULL; 61 char **hfirst, **hlast, **hp; 62 63 if (c_fc_depth != 0) { 64 bi_errorf("history function called recursively"); 65 return 1; 66 } 67 68 if (!Flag(FTALKING_I)) { 69 bi_errorf("history functions not available"); 70 return 1; 71 } 72 73 while ((optc = ksh_getopt(wp, &builtin_opt, 74 "e:glnrs0,1,2,3,4,5,6,7,8,9,")) != -1) 75 switch (optc) { 76 case 'e': 77 p = builtin_opt.optarg; 78 if (strcmp(p, "-") == 0) 79 sflag++; 80 else { 81 size_t len = strlen(p) + 4; 82 editor = str_nsave(p, len, ATEMP); 83 strlcat(editor, " $_", len); 84 } 85 break; 86 case 'g': /* non-at&t ksh */ 87 gflag++; 88 break; 89 case 'l': 90 lflag++; 91 break; 92 case 'n': 93 nflag++; 94 break; 95 case 'r': 96 rflag++; 97 break; 98 case 's': /* posix version of -e - */ 99 sflag++; 100 break; 101 /* kludge city - accept -num as -- -num (kind of) */ 102 case '0': case '1': case '2': case '3': case '4': 103 case '5': case '6': case '7': case '8': case '9': 104 p = shf_smprintf("-%c%s", 105 optc, builtin_opt.optarg); 106 if (!first) 107 first = p; 108 else if (!last) 109 last = p; 110 else { 111 bi_errorf("too many arguments"); 112 return 1; 113 } 114 break; 115 case '?': 116 return 1; 117 } 118 wp += builtin_opt.optind; 119 120 /* Substitute and execute command */ 121 if (sflag) { 122 char *pat = NULL, *rep = NULL; 123 124 if (editor || lflag || nflag || rflag) { 125 bi_errorf("can't use -e, -l, -n, -r with -s (-e -)"); 126 return 1; 127 } 128 129 /* Check for pattern replacement argument */ 130 if (*wp && **wp && (p = strchr(*wp + 1, '='))) { 131 pat = str_save(*wp, ATEMP); 132 p = pat + (p - *wp); 133 *p++ = '\0'; 134 rep = p; 135 wp++; 136 } 137 /* Check for search prefix */ 138 if (!first && (first = *wp)) 139 wp++; 140 if (last || *wp) { 141 bi_errorf("too many arguments"); 142 return 1; 143 } 144 145 hp = first ? hist_get(first, false, false) : 146 hist_get_newest(false); 147 if (!hp) 148 return 1; 149 c_fc_depth++; 150 ret = hist_replace(hp, pat, rep, gflag); 151 c_fc_reset(); 152 return ret; 153 } 154 155 if (editor && (lflag || nflag)) { 156 bi_errorf("can't use -l, -n with -e"); 157 return 1; 158 } 159 160 if (!first && (first = *wp)) 161 wp++; 162 if (!last && (last = *wp)) 163 wp++; 164 if (*wp) { 165 bi_errorf("too many arguments"); 166 return 1; 167 } 168 if (!first) { 169 hfirst = lflag ? hist_get("-16", true, true) : 170 hist_get_newest(false); 171 if (!hfirst) 172 return 1; 173 /* can't fail if hfirst didn't fail */ 174 hlast = hist_get_newest(false); 175 } else { 176 /* POSIX says not an error if first/last out of bounds 177 * when range is specified; at&t ksh and pdksh allow out of 178 * bounds for -l as well. 179 */ 180 hfirst = hist_get(first, (lflag || last) ? true : false, 181 lflag ? true : false); 182 if (!hfirst) 183 return 1; 184 hlast = last ? hist_get(last, true, lflag ? true : false) : 185 (lflag ? hist_get_newest(false) : hfirst); 186 if (!hlast) 187 return 1; 188 } 189 if (hfirst > hlast) { 190 char **temp; 191 192 temp = hfirst; hfirst = hlast; hlast = temp; 193 rflag = !rflag; /* POSIX */ 194 } 195 196 /* List history */ 197 if (lflag) { 198 char *s, *t; 199 const char *nfmt = nflag ? "\t" : "%d\t"; 200 201 for (hp = rflag ? hlast : hfirst; 202 hp >= hfirst && hp <= hlast; hp += rflag ? -1 : 1) { 203 shf_fprintf(shl_stdout, nfmt, 204 hist_source->line - (int) (histptr - hp)); 205 /* print multi-line commands correctly */ 206 for (s = *hp; (t = strchr(s, '\n')); s = t) 207 shf_fprintf(shl_stdout, "%.*s\t", ++t - s, s); 208 shf_fprintf(shl_stdout, "%s\n", s); 209 } 210 shf_flush(shl_stdout); 211 return 0; 212 } 213 214 /* Run editor on selected lines, then run resulting commands */ 215 216 tf = maketemp(ATEMP, TT_HIST_EDIT, &genv->temps); 217 if (!(shf = tf->shf)) { 218 bi_errorf("cannot create temp file %s - %s", 219 tf->name, strerror(errno)); 220 return 1; 221 } 222 for (hp = rflag ? hlast : hfirst; 223 hp >= hfirst && hp <= hlast; hp += rflag ? -1 : 1) 224 shf_fprintf(shf, "%s\n", *hp); 225 if (shf_close(shf) == EOF) { 226 bi_errorf("error writing temporary file - %s", strerror(errno)); 227 return 1; 228 } 229 230 /* Ignore setstr errors here (arbitrary) */ 231 setstr(local("_", false), tf->name, KSH_RETURN_ERROR); 232 233 /* XXX: source should not get trashed by this.. */ 234 { 235 Source *sold = source; 236 237 ret = command(editor ? editor : "${FCEDIT:-/bin/ed} $_", 0); 238 source = sold; 239 if (ret) 240 return ret; 241 } 242 243 { 244 struct stat statb; 245 XString xs; 246 char *xp; 247 int n; 248 249 if (!(shf = shf_open(tf->name, O_RDONLY, 0, 0))) { 250 bi_errorf("cannot open temp file %s", tf->name); 251 return 1; 252 } 253 254 n = fstat(shf->fd, &statb) == -1 ? 128 : 255 statb.st_size + 1; 256 Xinit(xs, xp, n, hist_source->areap); 257 while ((n = shf_read(xp, Xnleft(xs, xp), shf)) > 0) { 258 xp += n; 259 if (Xnleft(xs, xp) <= 0) 260 XcheckN(xs, xp, Xlength(xs, xp)); 261 } 262 if (n < 0) { 263 bi_errorf("error reading temp file %s - %s", 264 tf->name, strerror(shf->errno_)); 265 shf_close(shf); 266 return 1; 267 } 268 shf_close(shf); 269 *xp = '\0'; 270 strip_nuls(Xstring(xs, xp), Xlength(xs, xp)); 271 c_fc_depth++; 272 ret = hist_execute(Xstring(xs, xp)); 273 c_fc_reset(); 274 return ret; 275 } 276 } 277 278 /* Reset the c_fc depth counter. 279 * Made available for when an fc call is interrupted. 280 */ 281 void 282 c_fc_reset(void) 283 { 284 c_fc_depth = 0; 285 } 286 287 /* Save cmd in history, execute cmd (cmd gets trashed) */ 288 static int 289 hist_execute(char *cmd) 290 { 291 Source *sold; 292 int ret; 293 char *p, *q; 294 295 histbackup(); 296 297 for (p = cmd; p; p = q) { 298 if ((q = strchr(p, '\n'))) { 299 *q++ = '\0'; /* kill the newline */ 300 if (!*q) /* ignore trailing newline */ 301 q = NULL; 302 } 303 histsave(++(hist_source->line), p, 1); 304 305 shellf("%s\n", p); /* POSIX doesn't say this is done... */ 306 if ((p = q)) /* restore \n (trailing \n not restored) */ 307 q[-1] = '\n'; 308 } 309 310 /* Commands are executed here instead of pushing them onto the 311 * input 'cause posix says the redirection and variable assignments 312 * in 313 * X=y fc -e - 42 2> /dev/null 314 * are to effect the repeated commands environment. 315 */ 316 /* XXX: source should not get trashed by this.. */ 317 sold = source; 318 ret = command(cmd, 0); 319 source = sold; 320 return ret; 321 } 322 323 static int 324 hist_replace(char **hp, const char *pat, const char *rep, int global) 325 { 326 char *line; 327 328 if (!pat) 329 line = str_save(*hp, ATEMP); 330 else { 331 char *s, *s1; 332 int pat_len = strlen(pat); 333 int rep_len = strlen(rep); 334 int len; 335 XString xs; 336 char *xp; 337 int any_subst = 0; 338 339 Xinit(xs, xp, 128, ATEMP); 340 for (s = *hp; (s1 = strstr(s, pat)) && (!any_subst || global); 341 s = s1 + pat_len) { 342 any_subst = 1; 343 len = s1 - s; 344 XcheckN(xs, xp, len + rep_len); 345 memcpy(xp, s, len); /* first part */ 346 xp += len; 347 memcpy(xp, rep, rep_len); /* replacement */ 348 xp += rep_len; 349 } 350 if (!any_subst) { 351 bi_errorf("substitution failed"); 352 return 1; 353 } 354 len = strlen(s) + 1; 355 XcheckN(xs, xp, len); 356 memcpy(xp, s, len); 357 xp += len; 358 line = Xclose(xs, xp); 359 } 360 return hist_execute(line); 361 } 362 363 /* 364 * get pointer to history given pattern 365 * pattern is a number or string 366 */ 367 static char ** 368 hist_get(const char *str, int approx, int allow_cur) 369 { 370 char **hp = NULL; 371 int n; 372 373 if (getn(str, &n)) { 374 hp = histptr + (n < 0 ? n : (n - hist_source->line)); 375 if ((long)hp < (long)history) { 376 if (approx) 377 hp = hist_get_oldest(); 378 else { 379 bi_errorf("%s: not in history", str); 380 hp = NULL; 381 } 382 } else if (hp > histptr) { 383 if (approx) 384 hp = hist_get_newest(allow_cur); 385 else { 386 bi_errorf("%s: not in history", str); 387 hp = NULL; 388 } 389 } else if (!allow_cur && hp == histptr) { 390 bi_errorf("%s: invalid range", str); 391 hp = NULL; 392 } 393 } else { 394 int anchored = *str == '?' ? (++str, 0) : 1; 395 396 /* the -1 is to avoid the current fc command */ 397 n = findhist(histptr - history - 1, 0, str, anchored); 398 if (n < 0) { 399 bi_errorf("%s: not in history", str); 400 hp = NULL; 401 } else 402 hp = &history[n]; 403 } 404 return hp; 405 } 406 407 /* Return a pointer to the newest command in the history */ 408 char ** 409 hist_get_newest(int allow_cur) 410 { 411 if (histptr < history || (!allow_cur && histptr == history)) { 412 bi_errorf("no history (yet)"); 413 return NULL; 414 } 415 if (allow_cur) 416 return histptr; 417 return histptr - 1; 418 } 419 420 /* Return a pointer to the oldest command in the history */ 421 static char ** 422 hist_get_oldest(void) 423 { 424 if (histptr <= history) { 425 bi_errorf("no history (yet)"); 426 return NULL; 427 } 428 return history; 429 } 430 431 /******************************/ 432 /* Back up over last histsave */ 433 /******************************/ 434 static void 435 histbackup(void) 436 { 437 static int last_line = -1; 438 439 if (histptr >= history && last_line != hist_source->line) { 440 hist_source->line--; 441 afree(*histptr, APERM); 442 histptr--; 443 last_line = hist_source->line; 444 } 445 } 446 447 static void 448 histreset(void) 449 { 450 char **hp; 451 452 for (hp = history; hp <= histptr; hp++) 453 afree(*hp, APERM); 454 455 histptr = history - 1; 456 hist_source->line = 0; 457 } 458 459 /* 460 * Return the current position. 461 */ 462 char ** 463 histpos(void) 464 { 465 return current; 466 } 467 468 int 469 histnum(int n) 470 { 471 int last = histptr - history; 472 473 if (n < 0 || n >= last) { 474 current = histptr; 475 return last; 476 } else { 477 current = &history[n]; 478 return n; 479 } 480 } 481 482 /* 483 * This will become unnecessary if hist_get is modified to allow 484 * searching from positions other than the end, and in either 485 * direction. 486 */ 487 int 488 findhist(int start, int fwd, const char *str, int anchored) 489 { 490 char **hp; 491 int maxhist = histptr - history; 492 int incr = fwd ? 1 : -1; 493 int len = strlen(str); 494 495 if (start < 0 || start >= maxhist) 496 start = maxhist; 497 498 hp = &history[start]; 499 for (; hp >= history && hp <= histptr; hp += incr) 500 if ((anchored && strncmp(*hp, str, len) == 0) || 501 (!anchored && strstr(*hp, str))) 502 return hp - history; 503 504 return -1; 505 } 506 507 int 508 findhistrel(const char *str) 509 { 510 int maxhist = histptr - history; 511 int start = maxhist - 1; 512 int rec = atoi(str); 513 514 if (rec == 0) 515 return -1; 516 if (rec > 0) { 517 if (rec > maxhist) 518 return -1; 519 return rec - 1; 520 } 521 if (rec > maxhist) 522 return -1; 523 return start + rec + 1; 524 } 525 526 void 527 sethistcontrol(const char *str) 528 { 529 char *spec, *tok, *state; 530 531 ignorespace = 0; 532 ignoredups = 0; 533 534 if (str == NULL) 535 return; 536 537 spec = str_save(str, ATEMP); 538 for (tok = strtok_r(spec, ":", &state); tok != NULL; 539 tok = strtok_r(NULL, ":", &state)) { 540 if (strcmp(tok, "ignoredups") == 0) 541 ignoredups = 1; 542 else if (strcmp(tok, "ignorespace") == 0) 543 ignorespace = 1; 544 } 545 afree(spec, ATEMP); 546 } 547 548 /* 549 * set history 550 * this means reallocating the dataspace 551 */ 552 void 553 sethistsize(int n) 554 { 555 if (n > 0 && (uint32_t)n != histsize) { 556 char **tmp; 557 int offset = histptr - history; 558 559 /* save most recent history */ 560 if (offset > n - 1) { 561 char **hp; 562 563 offset = n - 1; 564 for (hp = history; hp < histptr - offset; hp++) 565 afree(*hp, APERM); 566 memmove(history, histptr - offset, n * sizeof(char *)); 567 } 568 569 tmp = reallocarray(histbase, n + 1, sizeof(char *)); 570 if (tmp != NULL) { 571 histbase = tmp; 572 histsize = n; 573 history = histbase + 1; 574 histptr = history + offset; 575 } else 576 warningf(false, "resizing history storage: %s", 577 strerror(errno)); 578 } 579 } 580 581 /* 582 * set history file 583 * This can mean reloading/resetting/starting history file 584 * maintenance 585 */ 586 void 587 sethistfile(const char *name) 588 { 589 /* if not started then nothing to do */ 590 if (hstarted == 0) 591 return; 592 593 /* if the name is the same as the name we have */ 594 if (hname && strcmp(hname, name) == 0) 595 return; 596 /* 597 * its a new name - possibly 598 */ 599 if (hname) { 600 afree(hname, APERM); 601 hname = NULL; 602 histreset(); 603 } 604 605 history_close(); 606 hist_init(hist_source); 607 } 608 609 /* 610 * initialise the history vector 611 */ 612 void 613 init_histvec(void) 614 { 615 if (histbase == NULL) { 616 histsize = HISTORYSIZE; 617 /* 618 * allocate one extra element so that histptr always 619 * lies within array bounds 620 */ 621 histbase = reallocarray(NULL, histsize + 1, sizeof(char *)); 622 if (histbase == NULL) 623 internal_errorf("allocating history storage: %s", 624 strerror(errno)); 625 *histbase = NULL; 626 history = histbase + 1; 627 histptr = history - 1; 628 } 629 } 630 631 static void 632 history_lock(int operation) 633 { 634 while (flock(fileno(histfh), operation) != 0) { 635 if (errno == EINTR || errno == EAGAIN) 636 continue; 637 else 638 break; 639 } 640 } 641 642 /* 643 * Routines added by Peter Collinson BSDI(Europe)/Hillside Systems to 644 * a) permit HISTSIZE to control number of lines of history stored 645 * b) maintain a physical history file 646 * 647 * It turns out that there is a lot of ghastly hackery here 648 */ 649 650 651 /* 652 * save command in history 653 */ 654 void 655 histsave(int lno, const char *cmd, int dowrite) 656 { 657 char *c, *cp; 658 659 if (ignorespace && cmd[0] == ' ') 660 return; 661 662 c = str_save(cmd, APERM); 663 if ((cp = strrchr(c, '\n')) != NULL) 664 *cp = '\0'; 665 666 /* 667 * XXX to properly check for duplicated lines we should first reload 668 * the histfile if needed 669 */ 670 if (ignoredups && histptr >= history && strcmp(*histptr, c) == 0) { 671 afree(c, APERM); 672 return; 673 } 674 675 if (dowrite && histfh) { 676 #ifndef SMALL 677 struct stat sb; 678 679 history_lock(LOCK_EX); 680 if (fstat(fileno(histfh), &sb) != -1) { 681 if (timespeccmp(&sb.st_mtim, &last_sb.st_mtim, ==)) 682 ; /* file is unchanged */ 683 else { 684 histreset(); 685 history_load(hist_source); 686 } 687 } 688 #endif 689 } 690 691 if (histptr < history + histsize - 1) 692 histptr++; 693 else { /* remove oldest command */ 694 afree(*history, APERM); 695 memmove(history, history + 1, 696 (histsize - 1) * sizeof(*history)); 697 } 698 *histptr = c; 699 700 if (dowrite && histfh) { 701 #ifndef SMALL 702 char *encoded; 703 704 /* append to file */ 705 if (fseeko(histfh, 0, SEEK_END) == 0 && 706 stravis(&encoded, c, VIS_SAFE | VIS_NL) != -1) { 707 fprintf(histfh, "%s\n", encoded); 708 fflush(histfh); 709 fstat(fileno(histfh), &last_sb); 710 line_co++; 711 history_write(); 712 free(encoded); 713 } 714 history_lock(LOCK_UN); 715 #endif 716 } 717 } 718 719 static FILE * 720 history_open(void) 721 { 722 FILE *f = NULL; 723 #ifndef SMALL 724 struct stat sb; 725 int fd, fddup; 726 727 if ((fd = open(hname, O_RDWR | O_CREAT | O_EXLOCK, 0600)) == -1) 728 return NULL; 729 if (fstat(fd, &sb) == -1 || sb.st_uid != getuid()) { 730 close(fd); 731 return NULL; 732 } 733 fddup = savefd(fd); 734 if (fddup != fd) 735 close(fd); 736 737 if ((f = fdopen(fddup, "r+")) == NULL) 738 close(fddup); 739 else 740 last_sb = sb; 741 #endif 742 return f; 743 } 744 745 static void 746 history_close(void) 747 { 748 if (histfh) { 749 fflush(histfh); 750 fclose(histfh); 751 histfh = NULL; 752 } 753 } 754 755 static void 756 history_load(Source *s) 757 { 758 char *p, encoded[LINE + 1], line[LINE + 1]; 759 int toolongseen = 0; 760 761 rewind(histfh); 762 line_co = 1; 763 764 /* just read it all; will auto resize history upon next command */ 765 while (fgets(encoded, sizeof(encoded), histfh)) { 766 if ((p = strchr(encoded, '\n')) == NULL) { 767 /* discard overlong line */ 768 do { 769 /* maybe a missing trailing newline? */ 770 if (strlen(encoded) != sizeof(encoded) - 1) { 771 bi_errorf("history file is corrupt"); 772 return; 773 } 774 } while (fgets(encoded, sizeof(encoded), histfh) 775 && strchr(encoded, '\n') == NULL); 776 777 if (!toolongseen) { 778 toolongseen = 1; 779 bi_errorf("ignored history line(s) longer than" 780 " %d bytes", LINE); 781 } 782 783 continue; 784 } 785 *p = '\0'; 786 s->line = line_co; 787 s->cmd_offset = line_co; 788 strunvis(line, encoded); 789 histsave(line_co, line, 0); 790 line_co++; 791 } 792 793 history_write(); 794 } 795 796 #define HMAGIC1 0xab 797 #define HMAGIC2 0xcd 798 799 void 800 hist_init(Source *s) 801 { 802 int oldmagic1, oldmagic2; 803 804 if (Flag(FTALKING) == 0) 805 return; 806 807 hstarted = 1; 808 809 hist_source = s; 810 811 if (str_val(global("HISTFILE")) == null) 812 return; 813 hname = str_save(str_val(global("HISTFILE")), APERM); 814 histfh = history_open(); 815 if (histfh == NULL) 816 return; 817 818 oldmagic1 = fgetc(histfh); 819 oldmagic2 = fgetc(histfh); 820 821 if (oldmagic1 == EOF || oldmagic2 == EOF) { 822 if (!feof(histfh) && ferror(histfh)) { 823 history_close(); 824 return; 825 } 826 } else if (oldmagic1 == HMAGIC1 && oldmagic2 == HMAGIC2) { 827 bi_errorf("ignoring old style history file"); 828 history_close(); 829 return; 830 } 831 832 history_load(s); 833 834 history_lock(LOCK_UN); 835 } 836 837 static void 838 history_write(void) 839 { 840 char **hp, *encoded; 841 842 /* see if file has grown over 25% */ 843 if (line_co < histsize + (histsize / 4)) 844 return; 845 846 /* rewrite the whole caboodle */ 847 rewind(histfh); 848 if (ftruncate(fileno(histfh), 0) == -1) { 849 bi_errorf("failed to rewrite history file - %s", 850 strerror(errno)); 851 } 852 for (hp = history; hp <= histptr; hp++) { 853 if (stravis(&encoded, *hp, VIS_SAFE | VIS_NL) != -1) { 854 if (fprintf(histfh, "%s\n", encoded) == -1) { 855 free(encoded); 856 return; 857 } 858 free(encoded); 859 } 860 } 861 862 line_co = histsize; 863 864 fflush(histfh); 865 fstat(fileno(histfh), &last_sb); 866 } 867 868 void 869 hist_finish(void) 870 { 871 history_close(); 872 } 873