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