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