1 /* $OpenBSD: history.c,v 1.86 2024/08/27 19:27:19 op 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 const char *errstr; 511 int maxhist = histptr - history; 512 int rec; 513 514 rec = strtonum(str, -maxhist, maxhist, &errstr); 515 if (errstr) 516 return -1; 517 518 if (rec == 0) 519 return -1; 520 if (rec > 0) 521 return rec - 1; 522 return maxhist + rec; 523 } 524 525 void 526 sethistcontrol(const char *str) 527 { 528 char *spec, *tok, *state; 529 530 ignorespace = 0; 531 ignoredups = 0; 532 533 if (str == NULL) 534 return; 535 536 spec = str_save(str, ATEMP); 537 for (tok = strtok_r(spec, ":", &state); tok != NULL; 538 tok = strtok_r(NULL, ":", &state)) { 539 if (strcmp(tok, "ignoredups") == 0) 540 ignoredups = 1; 541 else if (strcmp(tok, "ignorespace") == 0) 542 ignorespace = 1; 543 } 544 afree(spec, ATEMP); 545 } 546 547 /* 548 * set history 549 * this means reallocating the dataspace 550 */ 551 void 552 sethistsize(int n) 553 { 554 if (n > 0 && (uint32_t)n != histsize) { 555 char **tmp; 556 int offset = histptr - history; 557 558 /* save most recent history */ 559 if (offset > n - 1) { 560 char **hp; 561 562 offset = n - 1; 563 for (hp = history; hp < histptr - offset; hp++) 564 afree(*hp, APERM); 565 memmove(history, histptr - offset, n * sizeof(char *)); 566 } 567 568 tmp = reallocarray(histbase, n + 1, sizeof(char *)); 569 if (tmp != NULL) { 570 histbase = tmp; 571 histsize = n; 572 history = histbase + 1; 573 histptr = history + offset; 574 } else 575 warningf(false, "resizing history storage: %s", 576 strerror(errno)); 577 } 578 } 579 580 /* 581 * set history file 582 * This can mean reloading/resetting/starting history file 583 * maintenance 584 */ 585 void 586 sethistfile(const char *name) 587 { 588 /* if not started then nothing to do */ 589 if (hstarted == 0) 590 return; 591 592 /* if the name is the same as the name we have */ 593 if (hname && strcmp(hname, name) == 0) 594 return; 595 /* 596 * its a new name - possibly 597 */ 598 if (hname) { 599 afree(hname, APERM); 600 hname = NULL; 601 histreset(); 602 } 603 604 history_close(); 605 hist_init(hist_source); 606 } 607 608 /* 609 * initialise the history vector 610 */ 611 void 612 init_histvec(void) 613 { 614 if (histbase == NULL) { 615 histsize = HISTORYSIZE; 616 /* 617 * allocate one extra element so that histptr always 618 * lies within array bounds 619 */ 620 histbase = reallocarray(NULL, histsize + 1, sizeof(char *)); 621 if (histbase == NULL) 622 internal_errorf("allocating history storage: %s", 623 strerror(errno)); 624 *histbase = NULL; 625 history = histbase + 1; 626 histptr = history - 1; 627 } 628 } 629 630 static void 631 history_lock(int operation) 632 { 633 while (flock(fileno(histfh), operation) != 0) { 634 if (errno == EINTR || errno == EAGAIN) 635 continue; 636 else 637 break; 638 } 639 } 640 641 /* 642 * Routines added by Peter Collinson BSDI(Europe)/Hillside Systems to 643 * a) permit HISTSIZE to control number of lines of history stored 644 * b) maintain a physical history file 645 * 646 * It turns out that there is a lot of ghastly hackery here 647 */ 648 649 650 /* 651 * save command in history 652 */ 653 void 654 histsave(int lno, const char *cmd, int dowrite) 655 { 656 char *c, *cp; 657 658 if (ignorespace && cmd[0] == ' ') 659 return; 660 661 c = str_save(cmd, APERM); 662 if ((cp = strrchr(c, '\n')) != NULL) 663 *cp = '\0'; 664 665 /* 666 * XXX to properly check for duplicated lines we should first reload 667 * the histfile if needed 668 */ 669 if (ignoredups && histptr >= history && strcmp(*histptr, c) == 0) { 670 afree(c, APERM); 671 return; 672 } 673 674 if (dowrite && histfh) { 675 #ifndef SMALL 676 struct stat sb; 677 678 history_lock(LOCK_EX); 679 if (fstat(fileno(histfh), &sb) != -1) { 680 if (timespeccmp(&sb.st_mtim, &last_sb.st_mtim, ==)) 681 ; /* file is unchanged */ 682 else { 683 histreset(); 684 history_load(hist_source); 685 } 686 } 687 #endif 688 } 689 690 if (histptr < history + histsize - 1) 691 histptr++; 692 else { /* remove oldest command */ 693 afree(*history, APERM); 694 memmove(history, history + 1, 695 (histsize - 1) * sizeof(*history)); 696 } 697 *histptr = c; 698 699 if (dowrite && histfh) { 700 #ifndef SMALL 701 char *encoded; 702 703 /* append to file */ 704 if (fseeko(histfh, 0, SEEK_END) == 0 && 705 stravis(&encoded, c, VIS_SAFE | VIS_NL) != -1) { 706 fprintf(histfh, "%s\n", encoded); 707 fflush(histfh); 708 fstat(fileno(histfh), &last_sb); 709 line_co++; 710 history_write(); 711 free(encoded); 712 } 713 history_lock(LOCK_UN); 714 #endif 715 } 716 } 717 718 static FILE * 719 history_open(void) 720 { 721 FILE *f = NULL; 722 #ifndef SMALL 723 struct stat sb; 724 int fd, fddup; 725 726 if ((fd = open(hname, O_RDWR | O_CREAT | O_EXLOCK, 0600)) == -1) 727 return NULL; 728 if (fstat(fd, &sb) == -1 || sb.st_uid != getuid()) { 729 close(fd); 730 return NULL; 731 } 732 fddup = savefd(fd); 733 if (fddup != fd) 734 close(fd); 735 736 if ((f = fdopen(fddup, "r+")) == NULL) 737 close(fddup); 738 else 739 last_sb = sb; 740 #endif 741 return f; 742 } 743 744 static void 745 history_close(void) 746 { 747 if (histfh) { 748 fflush(histfh); 749 fclose(histfh); 750 histfh = NULL; 751 } 752 } 753 754 static void 755 history_load(Source *s) 756 { 757 char *p, encoded[LINE + 1], line[LINE + 1]; 758 int toolongseen = 0; 759 760 rewind(histfh); 761 line_co = 1; 762 763 /* just read it all; will auto resize history upon next command */ 764 while (fgets(encoded, sizeof(encoded), histfh)) { 765 if ((p = strchr(encoded, '\n')) == NULL) { 766 /* discard overlong line */ 767 do { 768 /* maybe a missing trailing newline? */ 769 if (strlen(encoded) != sizeof(encoded) - 1) { 770 bi_errorf("history file is corrupt"); 771 return; 772 } 773 } while (fgets(encoded, sizeof(encoded), histfh) 774 && strchr(encoded, '\n') == NULL); 775 776 if (!toolongseen) { 777 toolongseen = 1; 778 bi_errorf("ignored history line(s) longer than" 779 " %d bytes", LINE); 780 } 781 782 continue; 783 } 784 *p = '\0'; 785 s->line = line_co; 786 s->cmd_offset = line_co; 787 strunvis(line, encoded); 788 histsave(line_co, line, 0); 789 line_co++; 790 } 791 792 history_write(); 793 } 794 795 #define HMAGIC1 0xab 796 #define HMAGIC2 0xcd 797 798 void 799 hist_init(Source *s) 800 { 801 int oldmagic1, oldmagic2; 802 803 if (Flag(FTALKING) == 0) 804 return; 805 806 hstarted = 1; 807 808 hist_source = s; 809 810 if (str_val(global("HISTFILE")) == null) 811 return; 812 hname = str_save(str_val(global("HISTFILE")), APERM); 813 histfh = history_open(); 814 if (histfh == NULL) 815 return; 816 817 oldmagic1 = fgetc(histfh); 818 oldmagic2 = fgetc(histfh); 819 820 if (oldmagic1 == EOF || oldmagic2 == EOF) { 821 if (!feof(histfh) && ferror(histfh)) { 822 history_close(); 823 return; 824 } 825 } else if (oldmagic1 == HMAGIC1 && oldmagic2 == HMAGIC2) { 826 bi_errorf("ignoring old style history file"); 827 history_close(); 828 return; 829 } 830 831 history_load(s); 832 833 history_lock(LOCK_UN); 834 } 835 836 static void 837 history_write(void) 838 { 839 char **hp, *encoded; 840 841 /* see if file has grown over 25% */ 842 if (line_co < histsize + (histsize / 4)) 843 return; 844 845 /* rewrite the whole caboodle */ 846 rewind(histfh); 847 if (ftruncate(fileno(histfh), 0) == -1) { 848 bi_errorf("failed to rewrite history file - %s", 849 strerror(errno)); 850 } 851 for (hp = history; hp <= histptr; hp++) { 852 if (stravis(&encoded, *hp, VIS_SAFE | VIS_NL) != -1) { 853 if (fprintf(histfh, "%s\n", encoded) == -1) { 854 free(encoded); 855 return; 856 } 857 free(encoded); 858 } 859 } 860 861 line_co = histsize; 862 863 fflush(histfh); 864 fstat(fileno(histfh), &last_sb); 865 } 866 867 void 868 hist_finish(void) 869 { 870 history_close(); 871 } 872