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