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