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