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