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