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