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