history.c revision 1.6 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 (at) 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