1 /* $NetBSD: history.c,v 1.19 2018/05/08 16:37:59 kamil 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.19 2018/05/08 16:37:59 kamil Exp $"); 23 #endif 24 25 #include <sys/stat.h> 26 27 #include "sh.h" 28 29 #ifdef HISTORY 30 # ifdef EASY_HISTORY 31 32 # ifndef HISTFILE 33 # define HISTFILE ".pdksh_history" 34 # endif 35 36 # else 37 /* Defines and includes for the complicated case */ 38 39 # include <sys/file.h> 40 # include <sys/mman.h> 41 42 /* 43 * variables for handling the data file 44 */ 45 static int histfd; 46 static int hsize; 47 48 static int hist_count_lines ARGS((unsigned char *, int)); 49 static int hist_shrink ARGS((unsigned char *, int)); 50 static unsigned char *hist_skip_back ARGS((unsigned char *,int *,int)); 51 static void histload ARGS((Source *, unsigned char *, int)); 52 static void histinsert ARGS((Source *, int, unsigned char *)); 53 static void writehistfile ARGS((int, char *)); 54 static int sprinkle ARGS((int)); 55 56 # ifdef MAP_FILE 57 # define MAP_FLAGS (MAP_FILE|MAP_PRIVATE) 58 # else 59 # define MAP_FLAGS MAP_PRIVATE 60 # endif 61 62 # endif /* of EASY_HISTORY */ 63 64 static int hist_execute ARGS((char *)); 65 static int hist_replace ARGS((char **, const char *, const char *, int)); 66 static char **hist_get ARGS((const char *, int, int)); 67 static char **hist_get_newest ARGS((int)); 68 static char **hist_get_oldest ARGS((void)); 69 static void histbackup ARGS((void)); 70 71 static char **current; /* current position in history[] */ 72 static int curpos; /* current index in history[] */ 73 static char *hname; /* current name of history file */ 74 static int hstarted; /* set after hist_init() called */ 75 static Source *hist_source; 76 77 78 int 79 c_fc(wp) 80 char **wp; 81 { 82 struct shf *shf; 83 struct temp UNINITIALIZED(*tf); 84 char *p, *editor = (char *) 0; 85 int gflag = 0, lflag = 0, nflag = 0, sflag = 0, rflag = 0; 86 int optc; 87 char *first = (char *) 0, *last = (char *) 0; 88 char **hfirst, **hlast, **hp; 89 90 if (hist_source == NULL) { 91 bi_errorf("not interactive"); 92 return 1; 93 } 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, globalv) 339 char **hp; 340 const char *pat; 341 const char *rep; 342 int globalv; 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 || globalv) ; 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 == 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 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 int fd; 740 FILE *fh; 741 int i; 742 char **hp; 743 744 if (once++) 745 return; 746 if (hname == NULL || hname[0] == 0) 747 return; 748 749 /* check how many we have */ 750 i = histptr - histlist; 751 if (i >= histsize) 752 hp = &histptr[-histsize]; 753 else 754 hp = histlist; 755 756 if ((fd = open(hname, O_WRONLY | O_CREAT | O_TRUNC | O_EXLOCK, 0600)) != -1) { 757 /* Remove anything written before we got the lock */ 758 ftruncate(fd, 0); 759 if ((fh = fdopen(fd, "w")) != NULL) { 760 for (i = 0; hp + i <= histptr && hp[i]; i++) 761 fprintf(fh, "%s%c", hp[i], '\0'); 762 fclose(fh); 763 } 764 } 765 } 766 767 # else /* EASY_HISTORY */ 768 769 /* 770 * Routines added by Peter Collinson BSDI(Europe)/Hillside Systems to 771 * a) permit HISTSIZE to control number of lines of history stored 772 * b) maintain a physical history file 773 * 774 * It turns out that there is a lot of ghastly hackery here 775 */ 776 777 778 /* 779 * save command in history 780 */ 781 void 782 histsave(lno, cmd, dowrite) 783 int lno; 784 const char *cmd; 785 int dowrite; 786 { 787 char **hp; 788 char *c, *cp; 789 790 c = str_save(cmd, APERM); 791 if ((cp = strchr(c, '\n')) != NULL) 792 *cp = '\0'; 793 794 if (histfd && dowrite) 795 writehistfile(lno, c); 796 797 hp = histptr; 798 799 if (++hp >= histlist + histsize) { /* remove oldest command */ 800 afree((void*)*histlist, APERM); 801 for (hp = histlist; hp < histlist + histsize - 1; hp++) 802 hp[0] = hp[1]; 803 } 804 *hp = c; 805 histptr = hp; 806 } 807 808 /* 809 * Write history data to a file nominated by HISTFILE 810 * if HISTFILE is unset then history still happens, but 811 * the data is not written to a file 812 * All copies of ksh looking at the file will maintain the 813 * same history. This is ksh behaviour. 814 * 815 * This stuff uses mmap() 816 * if your system ain't got it - then you'll have to undef HISTORYFILE 817 */ 818 819 /* 820 * Open a history file 821 * Format is: 822 * Bytes 1, 2: HMAGIC - just to check that we are dealing with 823 * the correct object 824 * Then follows a number of stored commands 825 * Each command is 826 * <command byte><command number(4 bytes)><bytes><null> 827 */ 828 # define HMAGIC1 0xab 829 # define HMAGIC2 0xcd 830 # define COMMAND 0xff 831 832 void 833 hist_init(s) 834 Source *s; 835 { 836 unsigned char *base; 837 int lines; 838 int fd; 839 840 if (Flag(FTALKING) == 0) 841 return; 842 843 hstarted = 1; 844 845 hist_source = s; 846 847 hname = str_val(global("HISTFILE")); 848 if (hname == NULL) 849 return; 850 hname = str_save(hname, APERM); 851 852 retry: 853 /* we have a file and are interactive */ 854 if ((fd = open(hname, O_RDWR|O_CREAT|O_APPEND, 0600)) < 0) 855 return; 856 857 histfd = savefd(fd, 0); 858 859 (void) flock(histfd, LOCK_EX); 860 861 hsize = lseek(histfd, 0L, SEEK_END); 862 863 if (hsize == 0) { 864 /* add magic */ 865 if (sprinkle(histfd)) { 866 hist_finish(); 867 return; 868 } 869 } 870 else if (hsize > 0) { 871 /* 872 * we have some data 873 */ 874 base = (unsigned char *)mmap(0, hsize, PROT_READ, MAP_FLAGS, histfd, 0); 875 /* 876 * check on its validity 877 */ 878 if (base == MAP_FAILED || *base != HMAGIC1 || base[1] != HMAGIC2) { 879 if (base != MAP_FAILED) 880 munmap((caddr_t)base, hsize); 881 hist_finish(); 882 unlink(hname); 883 goto retry; 884 } 885 if (hsize > 2) { 886 lines = hist_count_lines(base+2, hsize-2); 887 if (lines > histsize) { 888 /* we need to make the file smaller */ 889 if (hist_shrink(base, hsize)) 890 unlink(hname); 891 munmap((caddr_t)base, hsize); 892 hist_finish(); 893 goto retry; 894 } 895 } 896 histload(hist_source, base+2, hsize-2); 897 munmap((caddr_t)base, hsize); 898 } 899 (void) flock(histfd, LOCK_UN); 900 hsize = lseek(histfd, 0L, SEEK_END); 901 } 902 903 typedef enum state { 904 shdr, /* expecting a header */ 905 sline, /* looking for a null byte to end the line */ 906 sn1, /* bytes 1 to 4 of a line no */ 907 sn2, sn3, sn4 908 } State; 909 910 static int 911 hist_count_lines(base, bytes) 912 unsigned char *base; 913 int bytes; 914 { 915 State state = shdr; 916 int lines = 0; 917 918 while (bytes--) { 919 switch (state) 920 { 921 case shdr: 922 if (*base == COMMAND) 923 state = sn1; 924 break; 925 case sn1: 926 state = sn2; break; 927 case sn2: 928 state = sn3; break; 929 case sn3: 930 state = sn4; break; 931 case sn4: 932 state = sline; break; 933 case sline: 934 if (*base == '\0') 935 lines++, state = shdr; 936 } 937 base++; 938 } 939 return lines; 940 } 941 942 /* 943 * Shrink the history file to histsize lines 944 */ 945 static int 946 hist_shrink(oldbase, oldbytes) 947 unsigned char *oldbase; 948 int oldbytes; 949 { 950 int fd; 951 char nfile[1024]; 952 struct stat statb; 953 unsigned char *nbase = oldbase; 954 int nbytes = oldbytes; 955 956 nbase = hist_skip_back(nbase, &nbytes, histsize); 957 if (nbase == NULL) 958 return 1; 959 if (nbase == oldbase) 960 return 0; 961 962 /* 963 * create temp file 964 */ 965 (void) shf_snprintf(nfile, sizeof(nfile), "%s.%d", hname, procpid); 966 if ((fd = creat(nfile, 0600)) < 0) 967 return 1; 968 969 if (sprinkle(fd)) { 970 close(fd); 971 unlink(nfile); 972 return 1; 973 } 974 if (write(fd, nbase, nbytes) != nbytes) { 975 close(fd); 976 unlink(nfile); 977 return 1; 978 } 979 /* 980 * worry about who owns this file 981 */ 982 if (fstat(histfd, &statb) >= 0) 983 fchown(fd, statb.st_uid, statb.st_gid); 984 close(fd); 985 986 /* 987 * rename 988 */ 989 if (rename(nfile, hname) < 0) 990 return 1; 991 return 0; 992 } 993 994 995 /* 996 * find a pointer to the data `no' back from the end of the file 997 * return the pointer and the number of bytes left 998 */ 999 static unsigned char * 1000 hist_skip_back(base, bytes, no) 1001 unsigned char *base; 1002 int *bytes; 1003 int no; 1004 { 1005 int lines = 0; 1006 unsigned char *ep; 1007 1008 for (ep = base + *bytes; --ep > base; ) { 1009 /* this doesn't really work: the 4 byte line number that is 1010 * encoded after the COMMAND byte can itself contain the 1011 * COMMAND byte.... 1012 */ 1013 for (; ep > base && *ep != COMMAND; ep--) 1014 ; 1015 if (ep == base) 1016 break; 1017 if (++lines == no) { 1018 *bytes = *bytes - ((char *)ep - (char *)base); 1019 return ep; 1020 } 1021 } 1022 return NULL; 1023 } 1024 1025 /* 1026 * load the history structure from the stored data 1027 */ 1028 static void 1029 histload(s, base, bytes) 1030 Source *s; 1031 unsigned char *base; 1032 int bytes; 1033 { 1034 State state; 1035 int lno = 0; 1036 unsigned char *line = NULL; 1037 1038 for (state = shdr; bytes-- > 0; base++) { 1039 switch (state) { 1040 case shdr: 1041 if (*base == COMMAND) 1042 state = sn1; 1043 break; 1044 case sn1: 1045 lno = (((*base)&0xff)<<24); 1046 state = sn2; 1047 break; 1048 case sn2: 1049 lno |= (((*base)&0xff)<<16); 1050 state = sn3; 1051 break; 1052 case sn3: 1053 lno |= (((*base)&0xff)<<8); 1054 state = sn4; 1055 break; 1056 case sn4: 1057 lno |= (*base)&0xff; 1058 line = base+1; 1059 state = sline; 1060 break; 1061 case sline: 1062 if (*base == '\0') { 1063 /* worry about line numbers */ 1064 if (histptr >= histlist && lno-1 != s->line) { 1065 /* a replacement ? */ 1066 histinsert(s, lno, line); 1067 } 1068 else { 1069 s->line = lno; 1070 histsave(lno, (char *)line, 0); 1071 } 1072 state = shdr; 1073 } 1074 } 1075 } 1076 } 1077 1078 /* 1079 * Insert a line into the history at a specified number 1080 */ 1081 static void 1082 histinsert(s, lno, line) 1083 Source *s; 1084 int lno; 1085 unsigned char *line; 1086 { 1087 char **hp; 1088 1089 if (lno >= s->line-(histptr-histlist) && lno <= s->line) { 1090 hp = &histptr[lno-s->line]; 1091 if (*hp) 1092 afree((void*)*hp, APERM); 1093 *hp = str_save((char *)line, APERM); 1094 } 1095 } 1096 1097 /* 1098 * write a command to the end of the history file 1099 * This *MAY* seem easy but it's also necessary to check 1100 * that the history file has not changed in size. 1101 * If it has - then some other shell has written to it 1102 * and we should read those commands to update our history 1103 */ 1104 static void 1105 writehistfile(lno, cmd) 1106 int lno; 1107 char *cmd; 1108 { 1109 int sizenow; 1110 unsigned char *base; 1111 unsigned char *new; 1112 int bytes; 1113 unsigned char hdr[5]; 1114 1115 (void) flock(histfd, LOCK_EX); 1116 sizenow = lseek(histfd, 0L, SEEK_END); 1117 if (sizenow != hsize) { 1118 /* 1119 * Things have changed 1120 */ 1121 if (sizenow > hsize) { 1122 /* someone has added some lines */ 1123 bytes = sizenow - hsize; 1124 base = (unsigned char *)mmap(0, sizenow, PROT_READ, MAP_FLAGS, histfd, 0); 1125 if (base == MAP_FAILED) 1126 goto bad; 1127 new = base + hsize; 1128 if (*new != COMMAND) { 1129 munmap((caddr_t)base, sizenow); 1130 goto bad; 1131 } 1132 hist_source->line--; 1133 histload(hist_source, new, bytes); 1134 hist_source->line++; 1135 lno = hist_source->line; 1136 munmap((caddr_t)base, sizenow); 1137 hsize = sizenow; 1138 } else { 1139 /* it has shrunk */ 1140 /* but to what? */ 1141 /* we'll give up for now */ 1142 goto bad; 1143 } 1144 } 1145 /* 1146 * we can write our bit now 1147 */ 1148 hdr[0] = COMMAND; 1149 hdr[1] = (lno>>24)&0xff; 1150 hdr[2] = (lno>>16)&0xff; 1151 hdr[3] = (lno>>8)&0xff; 1152 hdr[4] = lno&0xff; 1153 (void) write(histfd, hdr, 5); 1154 (void) write(histfd, cmd, strlen(cmd)+1); 1155 hsize = lseek(histfd, 0L, SEEK_END); 1156 (void) flock(histfd, LOCK_UN); 1157 return; 1158 bad: 1159 hist_finish(); 1160 } 1161 1162 void 1163 hist_finish() 1164 { 1165 (void) flock(histfd, LOCK_UN); 1166 (void) close(histfd); 1167 histfd = 0; 1168 } 1169 1170 /* 1171 * add magic to the history file 1172 */ 1173 static int 1174 sprinkle(fd) 1175 int fd; 1176 { 1177 static unsigned char mag[] = { HMAGIC1, HMAGIC2 }; 1178 1179 return(write(fd, mag, 2) != 2); 1180 } 1181 1182 # endif 1183 #else /* HISTORY */ 1184 1185 /* No history to be compiled in: dummy routines to avoid lots more ifdefs */ 1186 void 1187 init_histvec() 1188 { 1189 } 1190 void 1191 hist_init(s) 1192 Source *s; 1193 { 1194 } 1195 void 1196 hist_finish() 1197 { 1198 } 1199 void 1200 histsave(lno, cmd, dowrite) 1201 int lno; 1202 const char *cmd; 1203 int dowrite; 1204 { 1205 errorf("history not enabled"); 1206 } 1207 #endif /* HISTORY */ 1208