1 /* $OpenBSD: history.c,v 1.12 2000/10/12 10:51:13 art Exp $ */ 2 3 /* 4 * command history 5 * 6 * only implements in-memory history. 7 */ 8 9 /* 10 * This file contains 11 * a) the original in-memory history mechanism 12 * b) a simple file saving history mechanism done by sjg@zen 13 * define EASY_HISTORY to get this 14 * c) a more complicated mechanism done by pc@hillside.co.uk 15 * that more closely follows the real ksh way of doing 16 * things. You need to have the mmap system call for this 17 * to work on your system 18 */ 19 20 #include "sh.h" 21 #include "ksh_stat.h" 22 23 #ifdef HISTORY 24 # ifdef EASY_HISTORY 25 26 # ifndef HISTFILE 27 # ifdef OS2 28 # define HISTFILE "history.ksh" 29 # else /* OS2 */ 30 # define HISTFILE ".pdksh_history" 31 # endif /* OS2 */ 32 # endif 33 34 # else 35 /* Defines and includes for the complicated case */ 36 37 # include <sys/file.h> 38 # include <sys/mman.h> 39 40 /* 41 * variables for handling the data file 42 */ 43 static int histfd; 44 static int hsize; 45 46 static int hist_count_lines ARGS((unsigned char *, int)); 47 static int hist_shrink ARGS((unsigned char *, int)); 48 static unsigned char *hist_skip_back ARGS((unsigned char *,int *,int)); 49 static void histload ARGS((Source *, unsigned char *, int)); 50 static void histinsert ARGS((Source *, int, unsigned char *)); 51 static void writehistfile ARGS((int, char *)); 52 static int sprinkle ARGS((int)); 53 54 # ifdef MAP_FILE 55 # define MAP_FLAGS (MAP_FILE|MAP_PRIVATE) 56 # else 57 # define MAP_FLAGS MAP_PRIVATE 58 # endif 59 60 # endif /* of EASY_HISTORY */ 61 62 static int hist_execute ARGS((char *cmd)); 63 static int hist_replace ARGS((char **hp, const char *pat, const char *rep, 64 int global)); 65 static char **hist_get ARGS((const char *str, int approx, int allow_cur)); 66 static char **hist_get_newest ARGS((int allow_cur)); 67 static char **hist_get_oldest ARGS(()); 68 static void histbackup ARGS((void)); 69 70 static char **current; /* current postition in history[] */ 71 static int curpos; /* current index in history[] */ 72 static char *hname; /* current name of history file */ 73 static int hstarted; /* set after hist_init() called */ 74 static Source *hist_source; 75 76 77 int 78 c_fc(wp) 79 char **wp; 80 { 81 struct shf *shf; 82 struct temp UNINITIALIZED(*tf); 83 char *p, *editor = (char *) 0; 84 int gflag = 0, lflag = 0, nflag = 0, sflag = 0, rflag = 0; 85 int optc; 86 char *first = (char *) 0, *last = (char *) 0; 87 char **hfirst, **hlast, **hp; 88 89 while ((optc = ksh_getopt(wp, &builtin_opt, "e:glnrs0,1,2,3,4,5,6,7,8,9,")) != EOF) 90 switch (optc) { 91 case 'e': 92 p = builtin_opt.optarg; 93 if (strcmp(p, "-") == 0) 94 sflag++; 95 else { 96 editor = str_nsave(p, strlen(p) + 4, ATEMP); 97 strcat(editor, " $_"); 98 } 99 break; 100 case 'g': /* non-at&t ksh */ 101 gflag++; 102 break; 103 case 'l': 104 lflag++; 105 break; 106 case 'n': 107 nflag++; 108 break; 109 case 'r': 110 rflag++; 111 break; 112 case 's': /* posix version of -e - */ 113 sflag++; 114 break; 115 /* kludge city - accept -num as -- -num (kind of) */ 116 case '0': case '1': case '2': case '3': case '4': 117 case '5': case '6': case '7': case '8': case '9': 118 p = shf_smprintf("-%c%s", 119 optc, builtin_opt.optarg); 120 if (!first) 121 first = p; 122 else if (!last) 123 last = p; 124 else { 125 bi_errorf("too many arguments"); 126 return 1; 127 } 128 break; 129 case '?': 130 return 1; 131 } 132 wp += builtin_opt.optind; 133 134 /* Substitute and execute command */ 135 if (sflag) { 136 char *pat = (char *) 0, *rep = (char *) 0; 137 138 if (editor || lflag || nflag || rflag) { 139 bi_errorf("can't use -e, -l, -n, -r with -s (-e -)"); 140 return 1; 141 } 142 143 /* Check for pattern replacement argument */ 144 if (*wp && **wp && (p = strchr(*wp + 1, '='))) { 145 pat = str_save(*wp, ATEMP); 146 p = pat + (p - *wp); 147 *p++ = '\0'; 148 rep = p; 149 wp++; 150 } 151 /* Check for search prefix */ 152 if (!first && (first = *wp)) 153 wp++; 154 if (last || *wp) { 155 bi_errorf("too many arguments"); 156 return 1; 157 } 158 159 hp = first ? hist_get(first, FALSE, FALSE) 160 : hist_get_newest(FALSE); 161 if (!hp) 162 return 1; 163 return hist_replace(hp, pat, rep, gflag); 164 } 165 166 if (editor && (lflag || nflag)) { 167 bi_errorf("can't use -l, -n with -e"); 168 return 1; 169 } 170 171 if (!first && (first = *wp)) 172 wp++; 173 if (!last && (last = *wp)) 174 wp++; 175 if (*wp) { 176 bi_errorf("too many arguments"); 177 return 1; 178 } 179 if (!first) { 180 hfirst = lflag ? hist_get("-16", TRUE, TRUE) 181 : hist_get_newest(FALSE); 182 if (!hfirst) 183 return 1; 184 /* can't fail if hfirst didn't fail */ 185 hlast = hist_get_newest(FALSE); 186 } else { 187 /* POSIX says not an error if first/last out of bounds 188 * when range is specified; at&t ksh and pdksh allow out of 189 * bounds for -l as well. 190 */ 191 hfirst = hist_get(first, (lflag || last) ? TRUE : FALSE, 192 lflag ? TRUE : FALSE); 193 if (!hfirst) 194 return 1; 195 hlast = last ? hist_get(last, TRUE, lflag ? TRUE : FALSE) 196 : (lflag ? hist_get_newest(FALSE) : hfirst); 197 if (!hlast) 198 return 1; 199 } 200 if (hfirst > hlast) { 201 char **temp; 202 203 temp = hfirst; hfirst = hlast; hlast = temp; 204 rflag = !rflag; /* POSIX */ 205 } 206 207 /* List history */ 208 if (lflag) { 209 char *s, *t; 210 const char *nfmt = nflag ? "\t" : "%d\t"; 211 212 for (hp = rflag ? hlast : hfirst; 213 hp >= hfirst && hp <= hlast; hp += rflag ? -1 : 1) 214 { 215 shf_fprintf(shl_stdout, nfmt, 216 hist_source->line - (int) (histptr - hp)); 217 /* print multi-line commands correctly */ 218 for (s = *hp; (t = strchr(s, '\n')); s = t) 219 shf_fprintf(shl_stdout, "%.*s\t", ++t - s, s); 220 shf_fprintf(shl_stdout, "%s\n", s); 221 } 222 shf_flush(shl_stdout); 223 return 0; 224 } 225 226 /* Run editor on selected lines, then run resulting commands */ 227 228 tf = maketemp(ATEMP, TT_HIST_EDIT, &e->temps); 229 if (!(shf = tf->shf)) { 230 bi_errorf("cannot create temp file %s - %s", 231 tf->name, strerror(errno)); 232 return 1; 233 } 234 for (hp = rflag ? hlast : hfirst; 235 hp >= hfirst && hp <= hlast; hp += rflag ? -1 : 1) 236 shf_fprintf(shf, "%s\n", *hp); 237 if (shf_close(shf) == EOF) { 238 bi_errorf("error writing temporary file - %s", strerror(errno)); 239 return 1; 240 } 241 242 if (!Flag(FSH)) { 243 /* Ignore setstr errors here (arbitrary) */ 244 setstr(local("_", FALSE), tf->name, KSH_RETURN_ERROR); 245 } 246 247 /* XXX: source should not get trashed by this.. */ 248 { 249 Source *sold = source; 250 int ret; 251 252 ret = command(editor ? editor : "${FCEDIT:-/bin/ed} $_"); 253 source = sold; 254 if (ret) 255 return ret; 256 } 257 258 { 259 struct stat statb; 260 XString xs; 261 char *xp; 262 int n; 263 264 if (!(shf = shf_open(tf->name, O_RDONLY, 0, 0))) { 265 bi_errorf("cannot open temp file %s", tf->name); 266 return 1; 267 } 268 269 n = fstat(shf_fileno(shf), &statb) < 0 ? 128 270 : statb.st_size + 1; 271 Xinit(xs, xp, n, hist_source->areap); 272 while ((n = shf_read(xp, Xnleft(xs, xp), shf)) > 0) { 273 xp += n; 274 if (Xnleft(xs, xp) <= 0) 275 XcheckN(xs, xp, Xlength(xs, xp)); 276 } 277 if (n < 0) { 278 bi_errorf("error reading temp file %s - %s", 279 tf->name, strerror(shf_errno(shf))); 280 shf_close(shf); 281 return 1; 282 } 283 shf_close(shf); 284 *xp = '\0'; 285 strip_nuls(Xstring(xs, xp), Xlength(xs, xp)); 286 return hist_execute(Xstring(xs, xp)); 287 } 288 } 289 290 /* Save cmd in history, execute cmd (cmd gets trashed) */ 291 static int 292 hist_execute(cmd) 293 char *cmd; 294 { 295 Source *sold; 296 int ret; 297 char *p, *q; 298 299 histbackup(); 300 301 for (p = cmd; p; p = q) { 302 if ((q = strchr(p, '\n'))) { 303 *q++ = '\0'; /* kill the newline */ 304 if (!*q) /* ignore trailing newline */ 305 q = (char *) 0; 306 } 307 #ifdef EASY_HISTORY 308 if (p != cmd) 309 histappend(p, TRUE); 310 else 311 #endif /* EASY_HISTORY */ 312 histsave(++(hist_source->line), p, 1); 313 314 shellf("%s\n", p); /* POSIX doesn't say this is done... */ 315 if ((p = q)) /* restore \n (trailing \n not restored) */ 316 q[-1] = '\n'; 317 } 318 319 /* Commands are executed here instead of pushing them onto the 320 * input 'cause posix says the redirection and variable assignments 321 * in 322 * X=y fc -e - 42 2> /dev/null 323 * are to effect the repeated commands environment. 324 */ 325 /* XXX: source should not get trashed by this.. */ 326 sold = source; 327 ret = command(cmd); 328 source = sold; 329 return ret; 330 } 331 332 static int 333 hist_replace(hp, pat, rep, global) 334 char **hp; 335 const char *pat; 336 const char *rep; 337 int global; 338 { 339 char *line; 340 341 if (!pat) 342 line = str_save(*hp, ATEMP); 343 else { 344 char *s, *s1; 345 int pat_len = strlen(pat); 346 int rep_len = strlen(rep); 347 int len; 348 XString xs; 349 char *xp; 350 int any_subst = 0; 351 352 Xinit(xs, xp, 128, ATEMP); 353 for (s = *hp; (s1 = strstr(s, pat)) 354 && (!any_subst || global) ; s = s1 + pat_len) 355 { 356 any_subst = 1; 357 len = s1 - s; 358 XcheckN(xs, xp, len + rep_len); 359 memcpy(xp, s, len); /* first part */ 360 xp += len; 361 memcpy(xp, rep, rep_len); /* replacement */ 362 xp += rep_len; 363 } 364 if (!any_subst) { 365 bi_errorf("substitution failed"); 366 return 1; 367 } 368 len = strlen(s) + 1; 369 XcheckN(xs, xp, len); 370 memcpy(xp, s, len); 371 xp += len; 372 line = Xclose(xs, xp); 373 } 374 return hist_execute(line); 375 } 376 377 /* 378 * get pointer to history given pattern 379 * pattern is a number or string 380 */ 381 static char ** 382 hist_get(str, approx, allow_cur) 383 const char *str; 384 int approx; 385 int allow_cur; 386 { 387 char **hp = (char **) 0; 388 int n; 389 390 if (getn(str, &n)) { 391 hp = histptr + (n < 0 ? n : (n - hist_source->line)); 392 if (hp < history) { 393 if (approx) 394 hp = hist_get_oldest(); 395 else { 396 bi_errorf("%s: not in history", str); 397 hp = (char **) 0; 398 } 399 } else if (hp > histptr) { 400 if (approx) 401 hp = hist_get_newest(allow_cur); 402 else { 403 bi_errorf("%s: not in history", str); 404 hp = (char **) 0; 405 } 406 } else if (!allow_cur && hp == histptr) { 407 bi_errorf("%s: invalid range", str); 408 hp = (char **) 0; 409 } 410 } else { 411 int anchored = *str == '?' ? (++str, 0) : 1; 412 413 /* the -1 is to avoid the current fc command */ 414 n = findhist(histptr - history - 1, 0, str, anchored); 415 if (n < 0) { 416 bi_errorf("%s: not in history", str); 417 hp = (char **) 0; 418 } else 419 hp = &history[n]; 420 } 421 return hp; 422 } 423 424 /* Return a pointer to the newest command in the history */ 425 static char ** 426 hist_get_newest(allow_cur) 427 int allow_cur; 428 { 429 if (histptr < history || (!allow_cur && histptr == history)) { 430 bi_errorf("no history (yet)"); 431 return (char **) 0; 432 } 433 if (allow_cur) 434 return histptr; 435 return histptr - 1; 436 } 437 438 /* Return a pointer to the newest command in the history */ 439 static char ** 440 hist_get_oldest() 441 { 442 if (histptr <= history) { 443 bi_errorf("no history (yet)"); 444 return (char **) 0; 445 } 446 return history; 447 } 448 449 /******************************/ 450 /* Back up over last histsave */ 451 /******************************/ 452 static void 453 histbackup() 454 { 455 static int last_line = -1; 456 457 if (histptr >= history && last_line != hist_source->line) { 458 hist_source->line--; 459 afree((void*)*histptr, APERM); 460 histptr--; 461 last_line = hist_source->line; 462 } 463 } 464 465 /* 466 * Return the current position. 467 */ 468 char ** 469 histpos() 470 { 471 return current; 472 } 473 474 int 475 histN() 476 { 477 return curpos; 478 } 479 480 int 481 histnum(n) 482 int n; 483 { 484 int last = histptr - history; 485 486 if (n < 0 || n >= last) { 487 current = histptr; 488 curpos = last; 489 return last; 490 } else { 491 current = &history[n]; 492 curpos = n; 493 return n; 494 } 495 } 496 497 /* 498 * This will become unecessary if hist_get is modified to allow 499 * searching from positions other than the end, and in either 500 * direction. 501 */ 502 int 503 findhist(start, fwd, str, anchored) 504 int start; 505 int fwd; 506 const char *str; 507 int anchored; 508 { 509 char **hp; 510 int maxhist = histptr - history; 511 int incr = fwd ? 1 : -1; 512 int len = strlen(str); 513 514 if (start < 0 || start >= maxhist) 515 start = maxhist; 516 517 hp = &history[start]; 518 for (; hp >= history && hp <= histptr; hp += incr) 519 if ((anchored && strncmp(*hp, str, len) == 0) 520 || (!anchored && strstr(*hp, str))) 521 return hp - history; 522 523 return -1; 524 } 525 526 /* 527 * set history 528 * this means reallocating the dataspace 529 */ 530 void 531 sethistsize(n) 532 int n; 533 { 534 if (n > 0 && n != histsize) { 535 int cursize = histptr - history; 536 537 /* save most recent history */ 538 if (n < cursize) { 539 memmove(history, histptr - n, n * sizeof(char *)); 540 cursize = n; 541 } 542 543 history = (char **)aresize(history, n*sizeof(char *), APERM); 544 545 histsize = n; 546 histptr = history + cursize; 547 } 548 } 549 550 /* 551 * set history file 552 * This can mean reloading/resetting/starting history file 553 * maintenance 554 */ 555 void 556 sethistfile(name) 557 const char *name; 558 { 559 /* if not started then nothing to do */ 560 if (hstarted == 0) 561 return; 562 563 /* if the name is the same as the name we have */ 564 if (hname && strcmp(hname, name) == 0) 565 return; 566 567 /* 568 * its a new name - possibly 569 */ 570 # ifdef EASY_HISTORY 571 if (hname) { 572 afree(hname, APERM); 573 hname = NULL; 574 } 575 # else 576 if (histfd) { 577 /* yes the file is open */ 578 (void) close(histfd); 579 histfd = 0; 580 hsize = 0; 581 afree(hname, APERM); 582 hname = NULL; 583 /* let's reset the history */ 584 histptr = history - 1; 585 hist_source->line = 0; 586 } 587 # endif 588 589 hist_init(hist_source); 590 } 591 592 /* 593 * initialise the history vector 594 */ 595 void 596 init_histvec() 597 { 598 if (history == (char **)NULL) { 599 histsize = HISTORYSIZE; 600 history = (char **)alloc(histsize*sizeof (char *), APERM); 601 histptr = history - 1; 602 } 603 } 604 605 # ifdef EASY_HISTORY 606 /* 607 * save command in history 608 */ 609 void 610 histsave(lno, cmd, dowrite) 611 int lno; /* ignored (compatibility with COMPLEX_HISTORY) */ 612 const char *cmd; 613 int dowrite; /* ignored (compatibility with COMPLEX_HISTORY) */ 614 { 615 register char **hp = histptr; 616 char *cp; 617 618 if (++hp >= history + histsize) { /* remove oldest command */ 619 afree((void*)history[0], APERM); 620 memmove(history, history + 1, 621 sizeof(history[0]) * (histsize - 1)); 622 hp = &history[histsize - 1]; 623 } 624 *hp = str_save(cmd, APERM); 625 /* trash trailing newline but allow imbedded newlines */ 626 cp = *hp + strlen(*hp); 627 if (cp > *hp && cp[-1] == '\n') 628 cp[-1] = '\0'; 629 histptr = hp; 630 } 631 632 /* 633 * Append an entry to the last saved command. Used for multiline 634 * commands 635 */ 636 void 637 histappend(cmd, nl_separate) 638 const char *cmd; 639 int nl_separate; 640 { 641 int hlen, clen; 642 char *p; 643 644 hlen = strlen(*histptr); 645 clen = strlen(cmd); 646 if (clen > 0 && cmd[clen-1] == '\n') 647 clen--; 648 p = *histptr = (char *) aresize(*histptr, hlen + clen + 2, APERM); 649 p += hlen; 650 if (nl_separate) 651 *p++ = '\n'; 652 memcpy(p, cmd, clen); 653 p[clen] = '\0'; 654 } 655 656 /* 657 * 92-04-25 <sjg@zen> 658 * A simple history file implementation. 659 * At present we only save the history when we exit. 660 * This can cause problems when there are multiple shells are 661 * running under the same user-id. The last shell to exit gets 662 * to save its history. 663 */ 664 void 665 hist_init(s) 666 Source *s; 667 { 668 char *f; 669 FILE *fh; 670 671 if (Flag(FTALKING) == 0) 672 return; 673 674 hstarted = 1; 675 676 hist_source = s; 677 678 if ((f = str_val(global("HISTFILE"))) == NULL || *f == '\0') { 679 # if 1 /* Don't use history file unless the user asks for it */ 680 hname = NULL; 681 return; 682 # else 683 char *home = str_val(global("HOME")); 684 int len; 685 686 if (home == NULL) 687 home = null; 688 f = HISTFILE; 689 hname = alloc(len = strlen(home) + strlen(f) + 2, APERM); 690 shf_snprintf(hname, len, "%s/%s", home, f); 691 # endif 692 } else 693 hname = str_save(f, APERM); 694 695 if ((fh = fopen(hname, "r"))) { 696 int pos = 0, nread = 0; 697 int contin = 0; /* continuation of previous command */ 698 char *end; 699 char hline[LINE + 1]; 700 701 while (1) { 702 if (pos >= nread) { 703 pos = 0; 704 nread = fread(hline, 1, LINE, fh); 705 if (nread <= 0) 706 break; 707 hline[nread] = '\0'; 708 } 709 end = strchr(hline + pos, 0); /* will always succeed */ 710 if (contin) 711 histappend(hline + pos, 0); 712 else { 713 hist_source->line++; 714 histsave(0, hline + pos, 0); 715 } 716 pos = end - hline + 1; 717 contin = end == &hline[nread]; 718 } 719 fclose(fh); 720 } 721 } 722 723 /* 724 * save our history. 725 * We check that we do not have more than we are allowed. 726 * If the history file is read-only we do nothing. 727 * Handy for having all shells start with a useful history set. 728 */ 729 730 void 731 hist_finish() 732 { 733 static int once; 734 FILE *fh; 735 register int i; 736 register char **hp; 737 738 if (once++) 739 return; 740 /* check how many we have */ 741 i = histptr - history; 742 if (i >= histsize) 743 hp = &histptr[-histsize]; 744 else 745 hp = history; 746 if (hname && (fh = fopen(hname, "w"))) 747 { 748 for (i = 0; hp + i <= histptr && hp[i]; i++) 749 fprintf(fh, "%s%c", hp[i], '\0'); 750 fclose(fh); 751 } 752 } 753 754 # else /* EASY_HISTORY */ 755 756 /* 757 * Routines added by Peter Collinson BSDI(Europe)/Hillside Systems to 758 * a) permit HISTSIZE to control number of lines of history stored 759 * b) maintain a physical history file 760 * 761 * It turns out that there is a lot of ghastly hackery here 762 */ 763 764 765 /* 766 * save command in history 767 */ 768 void 769 histsave(lno, cmd, dowrite) 770 int lno; 771 const char *cmd; 772 int dowrite; 773 { 774 register char **hp; 775 char *c, *cp; 776 777 c = str_save(cmd, APERM); 778 if ((cp = strchr(c, '\n')) != NULL) 779 *cp = '\0'; 780 781 if (histfd && dowrite) 782 writehistfile(lno, c); 783 784 hp = histptr; 785 786 if (++hp >= history + histsize) { /* remove oldest command */ 787 afree((void*)*history, APERM); 788 for (hp = history; hp < history + histsize - 1; hp++) 789 hp[0] = hp[1]; 790 } 791 *hp = c; 792 histptr = hp; 793 } 794 795 /* 796 * Write history data to a file nominated by HISTFILE 797 * if HISTFILE is unset then history still happens, but 798 * the data is not written to a file 799 * All copies of ksh looking at the file will maintain the 800 * same history. This is ksh behaviour. 801 * 802 * This stuff uses mmap() 803 * if your system ain't got it - then you'll have to undef HISTORYFILE 804 */ 805 806 /* 807 * Open a history file 808 * Format is: 809 * Bytes 1, 2: HMAGIC - just to check that we are dealing with 810 * the correct object 811 * Then follows a number of stored commands 812 * Each command is 813 * <command byte><command number(4 bytes)><bytes><null> 814 */ 815 # define HMAGIC1 0xab 816 # define HMAGIC2 0xcd 817 # define COMMAND 0xff 818 819 void 820 hist_init(s) 821 Source *s; 822 { 823 unsigned char *base; 824 int lines; 825 int fd; 826 827 if (Flag(FTALKING) == 0) 828 return; 829 830 hstarted = 1; 831 832 hist_source = s; 833 834 hname = str_val(global("HISTFILE")); 835 if (hname == NULL) 836 return; 837 hname = str_save(hname, APERM); 838 839 retry: 840 /* we have a file and are interactive */ 841 if ((fd = open(hname, O_RDWR|O_CREAT|O_APPEND, 0600)) < 0) 842 return; 843 844 histfd = savefd(fd, 0); 845 846 (void) flock(histfd, LOCK_EX); 847 848 hsize = lseek(histfd, 0L, SEEK_END); 849 850 if (hsize == 0) { 851 /* add magic */ 852 if (sprinkle(histfd)) { 853 hist_finish(); 854 return; 855 } 856 } 857 else if (hsize > 0) { 858 /* 859 * we have some data 860 */ 861 base = (unsigned char *)mmap(0, hsize, PROT_READ, MAP_FLAGS, histfd, 0); 862 /* 863 * check on its validity 864 */ 865 if (base == MAP_FAILED || *base != HMAGIC1 || base[1] != HMAGIC2) { 866 if ((int)base != -1) 867 munmap((caddr_t)base, hsize); 868 hist_finish(); 869 unlink(hname); 870 goto retry; 871 } 872 if (hsize > 2) { 873 lines = hist_count_lines(base+2, hsize-2); 874 if (lines > histsize) { 875 /* we need to make the file smaller */ 876 if (hist_shrink(base, hsize)) 877 unlink(hname); 878 munmap((caddr_t)base, hsize); 879 hist_finish(); 880 goto retry; 881 } 882 } 883 histload(hist_source, base+2, hsize-2); 884 munmap((caddr_t)base, hsize); 885 } 886 (void) flock(histfd, LOCK_UN); 887 hsize = lseek(histfd, 0L, SEEK_END); 888 } 889 890 typedef enum state { 891 shdr, /* expecting a header */ 892 sline, /* looking for a null byte to end the line */ 893 sn1, /* bytes 1 to 4 of a line no */ 894 sn2, sn3, sn4, 895 } State; 896 897 static int 898 hist_count_lines(base, bytes) 899 register unsigned char *base; 900 register int bytes; 901 { 902 State state = shdr; 903 register lines = 0; 904 905 while (bytes--) { 906 switch (state) 907 { 908 case shdr: 909 if (*base == COMMAND) 910 state = sn1; 911 break; 912 case sn1: 913 state = sn2; break; 914 case sn2: 915 state = sn3; break; 916 case sn3: 917 state = sn4; break; 918 case sn4: 919 state = sline; break; 920 case sline: 921 if (*base == '\0') 922 lines++, state = shdr; 923 } 924 base++; 925 } 926 return lines; 927 } 928 929 /* 930 * Shrink the history file to histsize lines 931 */ 932 static int 933 hist_shrink(oldbase, oldbytes) 934 unsigned char *oldbase; 935 int oldbytes; 936 { 937 int fd; 938 char nfile[1024]; 939 struct stat statb; 940 unsigned char *nbase = oldbase; 941 int nbytes = oldbytes; 942 943 nbase = hist_skip_back(nbase, &nbytes, histsize); 944 if (nbase == NULL) 945 return 1; 946 if (nbase == oldbase) 947 return 0; 948 949 /* 950 * create temp file 951 */ 952 (void) shf_snprintf(nfile, sizeof(nfile), "%s.%d", hname, procpid); 953 if ((fd = creat(nfile, 0600)) < 0) 954 return 1; 955 956 if (sprinkle(fd)) { 957 close(fd); 958 unlink(nfile); 959 return 1; 960 } 961 if (write(fd, nbase, nbytes) != nbytes) { 962 close(fd); 963 unlink(nfile); 964 return 1; 965 } 966 /* 967 * worry about who owns this file 968 */ 969 if (fstat(histfd, &statb) >= 0) 970 fchown(fd, statb.st_uid, statb.st_gid); 971 close(fd); 972 973 /* 974 * rename 975 */ 976 if (rename(nfile, hname) < 0) 977 return 1; 978 return 0; 979 } 980 981 982 /* 983 * find a pointer to the data `no' back from the end of the file 984 * return the pointer and the number of bytes left 985 */ 986 static unsigned char * 987 hist_skip_back(base, bytes, no) 988 unsigned char *base; 989 int *bytes; 990 int no; 991 { 992 register int lines = 0; 993 register unsigned char *ep; 994 995 for (ep = base + *bytes; --ep > base; ) { 996 /* this doesn't really work: the 4 byte line number that is 997 * encoded after the COMMAND byte can itself contain the 998 * COMMAND byte.... 999 */ 1000 for (; ep > base && *ep != COMMAND; ep--) 1001 ; 1002 if (ep == base) 1003 break; 1004 if (++lines == no) { 1005 *bytes = *bytes - ((char *)ep - (char *)base); 1006 return ep; 1007 } 1008 } 1009 return NULL; 1010 } 1011 1012 /* 1013 * load the history structure from the stored data 1014 */ 1015 static void 1016 histload(s, base, bytes) 1017 Source *s; 1018 register unsigned char *base; 1019 register int bytes; 1020 { 1021 State state; 1022 int lno; 1023 unsigned char *line; 1024 1025 for (state = shdr; bytes-- > 0; base++) { 1026 switch (state) { 1027 case shdr: 1028 if (*base == COMMAND) 1029 state = sn1; 1030 break; 1031 case sn1: 1032 lno = (((*base)&0xff)<<24); 1033 state = sn2; 1034 break; 1035 case sn2: 1036 lno |= (((*base)&0xff)<<16); 1037 state = sn3; 1038 break; 1039 case sn3: 1040 lno |= (((*base)&0xff)<<8); 1041 state = sn4; 1042 break; 1043 case sn4: 1044 lno |= (*base)&0xff; 1045 line = base+1; 1046 state = sline; 1047 break; 1048 case sline: 1049 if (*base == '\0') { 1050 /* worry about line numbers */ 1051 if (histptr >= history && lno-1 != s->line) { 1052 /* a replacement ? */ 1053 histinsert(s, lno, line); 1054 } 1055 else { 1056 s->line = lno; 1057 histsave(lno, (char *)line, 0); 1058 } 1059 state = shdr; 1060 } 1061 } 1062 } 1063 } 1064 1065 /* 1066 * Insert a line into the history at a specified number 1067 */ 1068 static void 1069 histinsert(s, lno, line) 1070 Source *s; 1071 int lno; 1072 unsigned char *line; 1073 { 1074 register char **hp; 1075 1076 if (lno >= s->line-(histptr-history) && lno <= s->line) { 1077 hp = &histptr[lno-s->line]; 1078 if (*hp) 1079 afree((void*)*hp, APERM); 1080 *hp = str_save((char *)line, APERM); 1081 } 1082 } 1083 1084 /* 1085 * write a command to the end of the history file 1086 * This *MAY* seem easy but it's also necessary to check 1087 * that the history file has not changed in size. 1088 * If it has - then some other shell has written to it 1089 * and we should read those commands to update our history 1090 */ 1091 static void 1092 writehistfile(lno, cmd) 1093 int lno; 1094 char *cmd; 1095 { 1096 int sizenow; 1097 unsigned char *base; 1098 unsigned char *new; 1099 int bytes; 1100 char hdr[5]; 1101 1102 (void) flock(histfd, LOCK_EX); 1103 sizenow = lseek(histfd, 0L, SEEK_END); 1104 if (sizenow != hsize) { 1105 /* 1106 * Things have changed 1107 */ 1108 if (sizenow > hsize) { 1109 /* someone has added some lines */ 1110 bytes = sizenow - hsize; 1111 base = (unsigned char *)mmap(0, sizenow, PROT_READ, MAP_FLAGS, histfd, 0); 1112 if (base == MAP_FAILED) 1113 goto bad; 1114 new = base + hsize; 1115 if (*new != COMMAND) { 1116 munmap((caddr_t)base, sizenow); 1117 goto bad; 1118 } 1119 hist_source->line--; 1120 histload(hist_source, new, bytes); 1121 hist_source->line++; 1122 lno = hist_source->line; 1123 munmap((caddr_t)base, sizenow); 1124 hsize = sizenow; 1125 } else { 1126 /* it has shrunk */ 1127 /* but to what? */ 1128 /* we'll give up for now */ 1129 goto bad; 1130 } 1131 } 1132 /* 1133 * we can write our bit now 1134 */ 1135 hdr[0] = COMMAND; 1136 hdr[1] = (lno>>24)&0xff; 1137 hdr[2] = (lno>>16)&0xff; 1138 hdr[3] = (lno>>8)&0xff; 1139 hdr[4] = lno&0xff; 1140 (void) write(histfd, hdr, 5); 1141 (void) write(histfd, cmd, strlen(cmd)+1); 1142 hsize = lseek(histfd, 0L, SEEK_END); 1143 (void) flock(histfd, LOCK_UN); 1144 return; 1145 bad: 1146 hist_finish(); 1147 } 1148 1149 void 1150 hist_finish() 1151 { 1152 (void) flock(histfd, LOCK_UN); 1153 (void) close(histfd); 1154 histfd = 0; 1155 } 1156 1157 /* 1158 * add magic to the history file 1159 */ 1160 static int 1161 sprinkle(fd) 1162 int fd; 1163 { 1164 static char mag[] = { HMAGIC1, HMAGIC2 }; 1165 1166 return(write(fd, mag, 2) != 2); 1167 } 1168 1169 # endif 1170 #else /* HISTORY */ 1171 1172 /* No history to be compiled in: dummy routines to avoid lots more ifdefs */ 1173 void 1174 init_histvec() 1175 { 1176 } 1177 void 1178 hist_init(s) 1179 Source *s; 1180 { 1181 } 1182 void 1183 hist_finish() 1184 { 1185 } 1186 void 1187 histsave(lno, cmd, dowrite) 1188 int lno; 1189 const char *cmd; 1190 int dowrite; 1191 { 1192 errorf("history not enabled"); 1193 } 1194 #endif /* HISTORY */ 1195