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