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