1 /* $NetBSD: history.c,v 1.19 2018/05/08 16:37:59 kamil Exp $ */
2
3 /*
4 * command history
5 *
6 * only implements in-memory history.
7 */
8
9 /*
10 * This file contains
11 * a) the original in-memory history mechanism
12 * b) a simple file saving history mechanism done by sjg@zen
13 * define EASY_HISTORY to get this
14 * c) a more complicated mechanism done by pc@hillside.co.uk
15 * that more closely follows the real ksh way of doing
16 * things. You need to have the mmap system call for this
17 * to work on your system
18 */
19 #include <sys/cdefs.h>
20
21 #ifndef lint
22 __RCSID("$NetBSD: history.c,v 1.19 2018/05/08 16:37:59 kamil Exp $");
23 #endif
24
25 #include <sys/stat.h>
26
27 #include "sh.h"
28
29 #ifdef HISTORY
30 # ifdef EASY_HISTORY
31
32 # ifndef HISTFILE
33 # define HISTFILE ".pdksh_history"
34 # endif
35
36 # else
37 /* Defines and includes for the complicated case */
38
39 # include <sys/file.h>
40 # include <sys/mman.h>
41
42 /*
43 * variables for handling the data file
44 */
45 static int histfd;
46 static int hsize;
47
48 static int hist_count_lines ARGS((unsigned char *, int));
49 static int hist_shrink ARGS((unsigned char *, int));
50 static unsigned char *hist_skip_back ARGS((unsigned char *,int *,int));
51 static void histload ARGS((Source *, unsigned char *, int));
52 static void histinsert ARGS((Source *, int, unsigned char *));
53 static void writehistfile ARGS((int, char *));
54 static int sprinkle ARGS((int));
55
56 # ifdef MAP_FILE
57 # define MAP_FLAGS (MAP_FILE|MAP_PRIVATE)
58 # else
59 # define MAP_FLAGS MAP_PRIVATE
60 # endif
61
62 # endif /* of EASY_HISTORY */
63
64 static int hist_execute ARGS((char *));
65 static int hist_replace ARGS((char **, const char *, const char *, int));
66 static char **hist_get ARGS((const char *, int, int));
67 static char **hist_get_newest ARGS((int));
68 static char **hist_get_oldest ARGS((void));
69 static void histbackup ARGS((void));
70
71 static char **current; /* current position in history[] */
72 static int curpos; /* current index in history[] */
73 static char *hname; /* current name of history file */
74 static int hstarted; /* set after hist_init() called */
75 static Source *hist_source;
76
77
78 int
c_fc(wp)79 c_fc(wp)
80 char **wp;
81 {
82 struct shf *shf;
83 struct temp UNINITIALIZED(*tf);
84 char *p, *editor = (char *) 0;
85 int gflag = 0, lflag = 0, nflag = 0, sflag = 0, rflag = 0;
86 int optc;
87 char *first = (char *) 0, *last = (char *) 0;
88 char **hfirst, **hlast, **hp;
89
90 if (hist_source == NULL) {
91 bi_errorf("not interactive");
92 return 1;
93 }
94
95 while ((optc = ksh_getopt(wp, &builtin_opt, "e:glnrs0,1,2,3,4,5,6,7,8,9,")) != EOF)
96 switch (optc) {
97 case 'e':
98 p = builtin_opt.optarg;
99 if (strcmp(p, "-") == 0)
100 sflag++;
101 else {
102 size_t len = strlen(p) + 4;
103 editor = str_nsave(p, len, ATEMP);
104 strlcat(editor, " $_", len);
105 }
106 break;
107 case 'g': /* non-at&t ksh */
108 gflag++;
109 break;
110 case 'l':
111 lflag++;
112 break;
113 case 'n':
114 nflag++;
115 break;
116 case 'r':
117 rflag++;
118 break;
119 case 's': /* posix version of -e - */
120 sflag++;
121 break;
122 /* kludge city - accept -num as -- -num (kind of) */
123 case '0': case '1': case '2': case '3': case '4':
124 case '5': case '6': case '7': case '8': case '9':
125 p = shf_smprintf("-%c%s",
126 optc, builtin_opt.optarg);
127 if (!first)
128 first = p;
129 else if (!last)
130 last = p;
131 else {
132 bi_errorf("too many arguments");
133 return 1;
134 }
135 break;
136 case '?':
137 return 1;
138 }
139 wp += builtin_opt.optind;
140
141 /* Substitute and execute command */
142 if (sflag) {
143 char *pat = (char *) 0, *rep = (char *) 0;
144
145 if (editor || lflag || nflag || rflag) {
146 bi_errorf("can't use -e, -l, -n, -r with -s (-e -)");
147 return 1;
148 }
149
150 /* Check for pattern replacement argument */
151 if (*wp && **wp && (p = strchr(*wp + 1, '='))) {
152 pat = str_save(*wp, ATEMP);
153 p = pat + (p - *wp);
154 *p++ = '\0';
155 rep = p;
156 wp++;
157 }
158 /* Check for search prefix */
159 if (!first && (first = *wp))
160 wp++;
161 if (last || *wp) {
162 bi_errorf("too many arguments");
163 return 1;
164 }
165
166 hp = first ? hist_get(first, false, false)
167 : hist_get_newest(false);
168 if (!hp)
169 return 1;
170 return hist_replace(hp, pat, rep, gflag);
171 }
172
173 if (editor && (lflag || nflag)) {
174 bi_errorf("can't use -l, -n with -e");
175 return 1;
176 }
177
178 if (!first && (first = *wp))
179 wp++;
180 if (!last && (last = *wp))
181 wp++;
182 if (*wp) {
183 bi_errorf("too many arguments");
184 return 1;
185 }
186 if (!first) {
187 hfirst = lflag ? hist_get("-16", true, true)
188 : hist_get_newest(false);
189 if (!hfirst)
190 return 1;
191 /* can't fail if hfirst didn't fail */
192 hlast = hist_get_newest(false);
193 } else {
194 /* POSIX says not an error if first/last out of bounds
195 * when range is specified; at&t ksh and pdksh allow out of
196 * bounds for -l as well.
197 */
198 hfirst = hist_get(first, (lflag || last) ? true : false,
199 lflag ? true : false);
200 if (!hfirst)
201 return 1;
202 hlast = last ? hist_get(last, true, lflag ? true : false)
203 : (lflag ? hist_get_newest(false) : hfirst);
204 if (!hlast)
205 return 1;
206 }
207 if (hfirst > hlast) {
208 char **temp;
209
210 temp = hfirst; hfirst = hlast; hlast = temp;
211 rflag = !rflag; /* POSIX */
212 }
213
214 /* List history */
215 if (lflag) {
216 char *s, *t;
217 const char *nfmt = nflag ? "\t" : "%d\t";
218
219 for (hp = rflag ? hlast : hfirst;
220 hp >= hfirst && hp <= hlast; hp += rflag ? -1 : 1)
221 {
222 shf_fprintf(shl_stdout, nfmt,
223 hist_source->line - (int) (histptr - hp));
224 /* print multi-line commands correctly */
225 for (s = *hp; (t = strchr(s, '\n')); s = t)
226 shf_fprintf(shl_stdout, "%.*s\t", ++t - s, s);
227 shf_fprintf(shl_stdout, "%s\n", s);
228 }
229 shf_flush(shl_stdout);
230 return 0;
231 }
232
233 /* Run editor on selected lines, then run resulting commands */
234
235 tf = maketemp(ATEMP, TT_HIST_EDIT, &e->temps);
236 if (!(shf = tf->shf)) {
237 bi_errorf("cannot create temp file %s - %s",
238 tf->name, strerror(errno));
239 return 1;
240 }
241 for (hp = rflag ? hlast : hfirst;
242 hp >= hfirst && hp <= hlast; hp += rflag ? -1 : 1)
243 shf_fprintf(shf, "%s\n", *hp);
244 if (shf_close(shf) == EOF) {
245 bi_errorf("error writing temporary file - %s", strerror(errno));
246 return 1;
247 }
248
249 /* Ignore setstr errors here (arbitrary) */
250 setstr(local("_", false), tf->name, KSH_RETURN_ERROR);
251
252 /* XXX: source should not get trashed by this.. */
253 {
254 Source *sold = source;
255 int ret;
256
257 ret = command(editor ? editor : "${FCEDIT:-/bin/ed} $_");
258 source = sold;
259 if (ret)
260 return ret;
261 }
262
263 {
264 struct stat statb;
265 XString xs;
266 char *xp;
267 int n;
268
269 if (!(shf = shf_open(tf->name, O_RDONLY, 0, 0))) {
270 bi_errorf("cannot open temp file %s", tf->name);
271 return 1;
272 }
273
274 n = fstat(shf_fileno(shf), &statb) < 0 ? 128
275 : statb.st_size + 1;
276 Xinit(xs, xp, n, hist_source->areap);
277 while ((n = shf_read(xp, Xnleft(xs, xp), shf)) > 0) {
278 xp += n;
279 if (Xnleft(xs, xp) <= 0)
280 XcheckN(xs, xp, Xlength(xs, xp));
281 }
282 if (n < 0) {
283 bi_errorf("error reading temp file %s - %s",
284 tf->name, strerror(shf_errno(shf)));
285 shf_close(shf);
286 return 1;
287 }
288 shf_close(shf);
289 *xp = '\0';
290 strip_nuls(Xstring(xs, xp), Xlength(xs, xp));
291 return hist_execute(Xstring(xs, xp));
292 }
293 }
294
295 /* Save cmd in history, execute cmd (cmd gets trashed) */
296 static int
hist_execute(cmd)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
hist_replace(hp,pat,rep,globalv)338 hist_replace(hp, pat, rep, globalv)
339 char **hp;
340 const char *pat;
341 const char *rep;
342 int globalv;
343 {
344 char *line;
345
346 if (!pat)
347 line = str_save(*hp, ATEMP);
348 else {
349 char *s, *s1;
350 int pat_len = strlen(pat);
351 int rep_len = strlen(rep);
352 int len;
353 XString xs;
354 char *xp;
355 int any_subst = 0;
356
357 Xinit(xs, xp, 128, ATEMP);
358 for (s = *hp; (s1 = strstr(s, pat))
359 && (!any_subst || globalv) ; s = s1 + pat_len)
360 {
361 any_subst = 1;
362 len = s1 - s;
363 XcheckN(xs, xp, len + rep_len);
364 memcpy(xp, s, len); /* first part */
365 xp += len;
366 memcpy(xp, rep, rep_len); /* replacement */
367 xp += rep_len;
368 }
369 if (!any_subst) {
370 bi_errorf("substitution failed");
371 return 1;
372 }
373 len = strlen(s) + 1;
374 XcheckN(xs, xp, len);
375 memcpy(xp, s, len);
376 xp += len;
377 line = Xclose(xs, xp);
378 }
379 return hist_execute(line);
380 }
381
382 /*
383 * get pointer to history given pattern
384 * pattern is a number or string
385 */
386 static char **
hist_get(str,approx,allow_cur)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 **
hist_get_newest(allow_cur)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 **
hist_get_oldest()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
histbackup()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 **
histpos()474 histpos()
475 {
476 return current;
477 }
478
479 int
histN()480 histN()
481 {
482 return curpos;
483 }
484
485 int
histnum(n)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
findhist(start,fwd,str,anchored)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
sethistsize(n)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
sethistfile(name)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
init_histvec()601 init_histvec()
602 {
603 if (histlist == NULL) {
604 histsize = HISTORYSIZE;
605 histlist = (char **)alloc(histsize*sizeof (char *), APERM);
606 histptr = histlist - 1;
607 }
608 }
609
610 # ifdef EASY_HISTORY
611 /*
612 * save command in history
613 */
614 void
histsave(lno,cmd,dowrite)615 histsave(lno, cmd, dowrite)
616 int lno; /* ignored (compatibility with COMPLEX_HISTORY) */
617 const char *cmd;
618 int dowrite; /* ignored (compatibility with COMPLEX_HISTORY) */
619 {
620 char **hp = histptr;
621 char *cp;
622
623 if (++hp >= histlist + histsize) { /* remove oldest command */
624 afree((void*)histlist[0], APERM);
625 memmove(histlist, histlist + 1,
626 sizeof(histlist[0]) * (histsize - 1));
627 hp = &histlist[histsize - 1];
628 }
629 *hp = str_save(cmd, APERM);
630 /* trash trailing newline but allow imbedded newlines */
631 cp = *hp + strlen(*hp);
632 if (cp > *hp && cp[-1] == '\n')
633 cp[-1] = '\0';
634 histptr = hp;
635 }
636
637 /*
638 * Append an entry to the last saved command. Used for multiline
639 * commands
640 */
641 void
histappend(cmd,nl_separate)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
hist_init(s)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
hist_finish()736 hist_finish()
737 {
738 static int once;
739 int fd;
740 FILE *fh;
741 int i;
742 char **hp;
743
744 if (once++)
745 return;
746 if (hname == NULL || hname[0] == 0)
747 return;
748
749 /* check how many we have */
750 i = histptr - histlist;
751 if (i >= histsize)
752 hp = &histptr[-histsize];
753 else
754 hp = histlist;
755
756 if ((fd = open(hname, O_WRONLY | O_CREAT | O_TRUNC | O_EXLOCK, 0600)) != -1) {
757 /* Remove anything written before we got the lock */
758 ftruncate(fd, 0);
759 if ((fh = fdopen(fd, "w")) != NULL) {
760 for (i = 0; hp + i <= histptr && hp[i]; i++)
761 fprintf(fh, "%s%c", hp[i], '\0');
762 fclose(fh);
763 }
764 }
765 }
766
767 # else /* EASY_HISTORY */
768
769 /*
770 * Routines added by Peter Collinson BSDI(Europe)/Hillside Systems to
771 * a) permit HISTSIZE to control number of lines of history stored
772 * b) maintain a physical history file
773 *
774 * It turns out that there is a lot of ghastly hackery here
775 */
776
777
778 /*
779 * save command in history
780 */
781 void
histsave(lno,cmd,dowrite)782 histsave(lno, cmd, dowrite)
783 int lno;
784 const char *cmd;
785 int dowrite;
786 {
787 char **hp;
788 char *c, *cp;
789
790 c = str_save(cmd, APERM);
791 if ((cp = strchr(c, '\n')) != NULL)
792 *cp = '\0';
793
794 if (histfd && dowrite)
795 writehistfile(lno, c);
796
797 hp = histptr;
798
799 if (++hp >= histlist + histsize) { /* remove oldest command */
800 afree((void*)*histlist, APERM);
801 for (hp = histlist; hp < histlist + histsize - 1; hp++)
802 hp[0] = hp[1];
803 }
804 *hp = c;
805 histptr = hp;
806 }
807
808 /*
809 * Write history data to a file nominated by HISTFILE
810 * if HISTFILE is unset then history still happens, but
811 * the data is not written to a file
812 * All copies of ksh looking at the file will maintain the
813 * same history. This is ksh behaviour.
814 *
815 * This stuff uses mmap()
816 * if your system ain't got it - then you'll have to undef HISTORYFILE
817 */
818
819 /*
820 * Open a history file
821 * Format is:
822 * Bytes 1, 2: HMAGIC - just to check that we are dealing with
823 * the correct object
824 * Then follows a number of stored commands
825 * Each command is
826 * <command byte><command number(4 bytes)><bytes><null>
827 */
828 # define HMAGIC1 0xab
829 # define HMAGIC2 0xcd
830 # define COMMAND 0xff
831
832 void
hist_init(s)833 hist_init(s)
834 Source *s;
835 {
836 unsigned char *base;
837 int lines;
838 int fd;
839
840 if (Flag(FTALKING) == 0)
841 return;
842
843 hstarted = 1;
844
845 hist_source = s;
846
847 hname = str_val(global("HISTFILE"));
848 if (hname == NULL)
849 return;
850 hname = str_save(hname, APERM);
851
852 retry:
853 /* we have a file and are interactive */
854 if ((fd = open(hname, O_RDWR|O_CREAT|O_APPEND, 0600)) < 0)
855 return;
856
857 histfd = savefd(fd, 0);
858
859 (void) flock(histfd, LOCK_EX);
860
861 hsize = lseek(histfd, 0L, SEEK_END);
862
863 if (hsize == 0) {
864 /* add magic */
865 if (sprinkle(histfd)) {
866 hist_finish();
867 return;
868 }
869 }
870 else if (hsize > 0) {
871 /*
872 * we have some data
873 */
874 base = (unsigned char *)mmap(0, hsize, PROT_READ, MAP_FLAGS, histfd, 0);
875 /*
876 * check on its validity
877 */
878 if (base == MAP_FAILED || *base != HMAGIC1 || base[1] != HMAGIC2) {
879 if (base != MAP_FAILED)
880 munmap((caddr_t)base, hsize);
881 hist_finish();
882 unlink(hname);
883 goto retry;
884 }
885 if (hsize > 2) {
886 lines = hist_count_lines(base+2, hsize-2);
887 if (lines > histsize) {
888 /* we need to make the file smaller */
889 if (hist_shrink(base, hsize))
890 unlink(hname);
891 munmap((caddr_t)base, hsize);
892 hist_finish();
893 goto retry;
894 }
895 }
896 histload(hist_source, base+2, hsize-2);
897 munmap((caddr_t)base, hsize);
898 }
899 (void) flock(histfd, LOCK_UN);
900 hsize = lseek(histfd, 0L, SEEK_END);
901 }
902
903 typedef enum state {
904 shdr, /* expecting a header */
905 sline, /* looking for a null byte to end the line */
906 sn1, /* bytes 1 to 4 of a line no */
907 sn2, sn3, sn4
908 } State;
909
910 static int
hist_count_lines(base,bytes)911 hist_count_lines(base, bytes)
912 unsigned char *base;
913 int bytes;
914 {
915 State state = shdr;
916 int lines = 0;
917
918 while (bytes--) {
919 switch (state)
920 {
921 case shdr:
922 if (*base == COMMAND)
923 state = sn1;
924 break;
925 case sn1:
926 state = sn2; break;
927 case sn2:
928 state = sn3; break;
929 case sn3:
930 state = sn4; break;
931 case sn4:
932 state = sline; break;
933 case sline:
934 if (*base == '\0')
935 lines++, state = shdr;
936 }
937 base++;
938 }
939 return lines;
940 }
941
942 /*
943 * Shrink the history file to histsize lines
944 */
945 static int
hist_shrink(oldbase,oldbytes)946 hist_shrink(oldbase, oldbytes)
947 unsigned char *oldbase;
948 int oldbytes;
949 {
950 int fd;
951 char nfile[1024];
952 struct stat statb;
953 unsigned char *nbase = oldbase;
954 int nbytes = oldbytes;
955
956 nbase = hist_skip_back(nbase, &nbytes, histsize);
957 if (nbase == NULL)
958 return 1;
959 if (nbase == oldbase)
960 return 0;
961
962 /*
963 * create temp file
964 */
965 (void) shf_snprintf(nfile, sizeof(nfile), "%s.%d", hname, procpid);
966 if ((fd = creat(nfile, 0600)) < 0)
967 return 1;
968
969 if (sprinkle(fd)) {
970 close(fd);
971 unlink(nfile);
972 return 1;
973 }
974 if (write(fd, nbase, nbytes) != nbytes) {
975 close(fd);
976 unlink(nfile);
977 return 1;
978 }
979 /*
980 * worry about who owns this file
981 */
982 if (fstat(histfd, &statb) >= 0)
983 fchown(fd, statb.st_uid, statb.st_gid);
984 close(fd);
985
986 /*
987 * rename
988 */
989 if (rename(nfile, hname) < 0)
990 return 1;
991 return 0;
992 }
993
994
995 /*
996 * find a pointer to the data `no' back from the end of the file
997 * return the pointer and the number of bytes left
998 */
999 static unsigned char *
hist_skip_back(base,bytes,no)1000 hist_skip_back(base, bytes, no)
1001 unsigned char *base;
1002 int *bytes;
1003 int no;
1004 {
1005 int lines = 0;
1006 unsigned char *ep;
1007
1008 for (ep = base + *bytes; --ep > base; ) {
1009 /* this doesn't really work: the 4 byte line number that is
1010 * encoded after the COMMAND byte can itself contain the
1011 * COMMAND byte....
1012 */
1013 for (; ep > base && *ep != COMMAND; ep--)
1014 ;
1015 if (ep == base)
1016 break;
1017 if (++lines == no) {
1018 *bytes = *bytes - ((char *)ep - (char *)base);
1019 return ep;
1020 }
1021 }
1022 return NULL;
1023 }
1024
1025 /*
1026 * load the history structure from the stored data
1027 */
1028 static void
histload(s,base,bytes)1029 histload(s, base, bytes)
1030 Source *s;
1031 unsigned char *base;
1032 int bytes;
1033 {
1034 State state;
1035 int lno = 0;
1036 unsigned char *line = NULL;
1037
1038 for (state = shdr; bytes-- > 0; base++) {
1039 switch (state) {
1040 case shdr:
1041 if (*base == COMMAND)
1042 state = sn1;
1043 break;
1044 case sn1:
1045 lno = (((*base)&0xff)<<24);
1046 state = sn2;
1047 break;
1048 case sn2:
1049 lno |= (((*base)&0xff)<<16);
1050 state = sn3;
1051 break;
1052 case sn3:
1053 lno |= (((*base)&0xff)<<8);
1054 state = sn4;
1055 break;
1056 case sn4:
1057 lno |= (*base)&0xff;
1058 line = base+1;
1059 state = sline;
1060 break;
1061 case sline:
1062 if (*base == '\0') {
1063 /* worry about line numbers */
1064 if (histptr >= histlist && lno-1 != s->line) {
1065 /* a replacement ? */
1066 histinsert(s, lno, line);
1067 }
1068 else {
1069 s->line = lno;
1070 histsave(lno, (char *)line, 0);
1071 }
1072 state = shdr;
1073 }
1074 }
1075 }
1076 }
1077
1078 /*
1079 * Insert a line into the history at a specified number
1080 */
1081 static void
histinsert(s,lno,line)1082 histinsert(s, lno, line)
1083 Source *s;
1084 int lno;
1085 unsigned char *line;
1086 {
1087 char **hp;
1088
1089 if (lno >= s->line-(histptr-histlist) && lno <= s->line) {
1090 hp = &histptr[lno-s->line];
1091 if (*hp)
1092 afree((void*)*hp, APERM);
1093 *hp = str_save((char *)line, APERM);
1094 }
1095 }
1096
1097 /*
1098 * write a command to the end of the history file
1099 * This *MAY* seem easy but it's also necessary to check
1100 * that the history file has not changed in size.
1101 * If it has - then some other shell has written to it
1102 * and we should read those commands to update our history
1103 */
1104 static void
writehistfile(lno,cmd)1105 writehistfile(lno, cmd)
1106 int lno;
1107 char *cmd;
1108 {
1109 int sizenow;
1110 unsigned char *base;
1111 unsigned char *new;
1112 int bytes;
1113 unsigned char hdr[5];
1114
1115 (void) flock(histfd, LOCK_EX);
1116 sizenow = lseek(histfd, 0L, SEEK_END);
1117 if (sizenow != hsize) {
1118 /*
1119 * Things have changed
1120 */
1121 if (sizenow > hsize) {
1122 /* someone has added some lines */
1123 bytes = sizenow - hsize;
1124 base = (unsigned char *)mmap(0, sizenow, PROT_READ, MAP_FLAGS, histfd, 0);
1125 if (base == MAP_FAILED)
1126 goto bad;
1127 new = base + hsize;
1128 if (*new != COMMAND) {
1129 munmap((caddr_t)base, sizenow);
1130 goto bad;
1131 }
1132 hist_source->line--;
1133 histload(hist_source, new, bytes);
1134 hist_source->line++;
1135 lno = hist_source->line;
1136 munmap((caddr_t)base, sizenow);
1137 hsize = sizenow;
1138 } else {
1139 /* it has shrunk */
1140 /* but to what? */
1141 /* we'll give up for now */
1142 goto bad;
1143 }
1144 }
1145 /*
1146 * we can write our bit now
1147 */
1148 hdr[0] = COMMAND;
1149 hdr[1] = (lno>>24)&0xff;
1150 hdr[2] = (lno>>16)&0xff;
1151 hdr[3] = (lno>>8)&0xff;
1152 hdr[4] = lno&0xff;
1153 (void) write(histfd, hdr, 5);
1154 (void) write(histfd, cmd, strlen(cmd)+1);
1155 hsize = lseek(histfd, 0L, SEEK_END);
1156 (void) flock(histfd, LOCK_UN);
1157 return;
1158 bad:
1159 hist_finish();
1160 }
1161
1162 void
hist_finish()1163 hist_finish()
1164 {
1165 (void) flock(histfd, LOCK_UN);
1166 (void) close(histfd);
1167 histfd = 0;
1168 }
1169
1170 /*
1171 * add magic to the history file
1172 */
1173 static int
sprinkle(fd)1174 sprinkle(fd)
1175 int fd;
1176 {
1177 static unsigned char mag[] = { HMAGIC1, HMAGIC2 };
1178
1179 return(write(fd, mag, 2) != 2);
1180 }
1181
1182 # endif
1183 #else /* HISTORY */
1184
1185 /* No history to be compiled in: dummy routines to avoid lots more ifdefs */
1186 void
init_histvec()1187 init_histvec()
1188 {
1189 }
1190 void
hist_init(s)1191 hist_init(s)
1192 Source *s;
1193 {
1194 }
1195 void
hist_finish()1196 hist_finish()
1197 {
1198 }
1199 void
histsave(lno,cmd,dowrite)1200 histsave(lno, cmd, dowrite)
1201 int lno;
1202 const char *cmd;
1203 int dowrite;
1204 {
1205 errorf("history not enabled");
1206 }
1207 #endif /* HISTORY */
1208