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