xref: /minix/bin/ksh/history.c (revision 84d9c625)
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