xref: /netbsd-src/bin/ksh/history.c (revision 969da55f1ccd8f94fb0d823d2bcc57601e67e15f)
1 /*	$NetBSD: history.c,v 1.7 2004/07/07 19:20:09 mycroft 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.7 2004/07/07 19:20:09 mycroft 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 position 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 				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
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
338 hist_replace(hp, pat, rep, global)
339 	char **hp;
340 	const char *pat;
341 	const char *rep;
342 	int global;
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 || global) ; 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 **
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 **
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 **
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
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 **
474 histpos()
475 {
476 	return current;
477 }
478 
479 int
480 histN()
481 {
482 	return curpos;
483 }
484 
485 int
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
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
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
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
601 init_histvec()
602 {
603 	if (histlist == (char **)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
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 	register 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
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
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
736 hist_finish()
737 {
738   static int once;
739   FILE *fh;
740   register int i;
741   register char **hp;
742 
743   if (once++)
744     return;
745   /* check how many we have */
746   i = histptr - histlist;
747   if (i >= histsize)
748     hp = &histptr[-histsize];
749   else
750     hp = histlist;
751   if (hname && (fh = fopen(hname, "w")))
752   {
753     for (i = 0; hp + i <= histptr && hp[i]; i++)
754       fprintf(fh, "%s%c", hp[i], '\0');
755     fclose(fh);
756   }
757 }
758 
759 # else /* EASY_HISTORY */
760 
761 /*
762  *	Routines added by Peter Collinson BSDI(Europe)/Hillside Systems to
763  *	a) permit HISTSIZE to control number of lines of history stored
764  *	b) maintain a physical history file
765  *
766  *	It turns out that there is a lot of ghastly hackery here
767  */
768 
769 
770 /*
771  * save command in history
772  */
773 void
774 histsave(lno, cmd, dowrite)
775 	int lno;
776 	const char *cmd;
777 	int dowrite;
778 {
779 	register char **hp;
780 	char *c, *cp;
781 
782 	c = str_save(cmd, APERM);
783 	if ((cp = strchr(c, '\n')) != NULL)
784 		*cp = '\0';
785 
786 	if (histfd && dowrite)
787 		writehistfile(lno, c);
788 
789 	hp = histptr;
790 
791 	if (++hp >= histlist + histsize) { /* remove oldest command */
792 		afree((void*)*histlist, APERM);
793 		for (hp = histlist; hp < histlist + histsize - 1; hp++)
794 			hp[0] = hp[1];
795 	}
796 	*hp = c;
797 	histptr = hp;
798 }
799 
800 /*
801  *	Write history data to a file nominated by HISTFILE
802  *	if HISTFILE is unset then history still happens, but
803  *	the data is not written to a file
804  *	All copies of ksh looking at the file will maintain the
805  *	same history. This is ksh behaviour.
806  *
807  *	This stuff uses mmap()
808  *	if your system ain't got it - then you'll have to undef HISTORYFILE
809  */
810 
811 /*
812  *	Open a history file
813  *	Format is:
814  *	Bytes 1, 2: HMAGIC - just to check that we are dealing with
815  *		    the correct object
816  *	Then follows a number of stored commands
817  *	Each command is
818  *	<command byte><command number(4 bytes)><bytes><null>
819  */
820 # define HMAGIC1		0xab
821 # define HMAGIC2		0xcd
822 # define COMMAND		0xff
823 
824 void
825 hist_init(s)
826 	Source *s;
827 {
828 	unsigned char	*base;
829 	int	lines;
830 	int	fd;
831 
832 	if (Flag(FTALKING) == 0)
833 		return;
834 
835 	hstarted = 1;
836 
837 	hist_source = s;
838 
839 	hname = str_val(global("HISTFILE"));
840 	if (hname == NULL)
841 		return;
842 	hname = str_save(hname, APERM);
843 
844   retry:
845 	/* we have a file and are interactive */
846 	if ((fd = open(hname, O_RDWR|O_CREAT|O_APPEND, 0600)) < 0)
847 		return;
848 
849 	histfd = savefd(fd, 0);
850 
851 	(void) flock(histfd, LOCK_EX);
852 
853 	hsize = lseek(histfd, 0L, SEEK_END);
854 
855 	if (hsize == 0) {
856 		/* add magic */
857 		if (sprinkle(histfd)) {
858 			hist_finish();
859 			return;
860 		}
861 	}
862 	else if (hsize > 0) {
863 		/*
864 		 * we have some data
865 		 */
866 		base = (unsigned char *)mmap(0, hsize, PROT_READ, MAP_FLAGS, histfd, 0);
867 		/*
868 		 * check on its validity
869 		 */
870 		if (base == MAP_FAILED || *base != HMAGIC1 || base[1] != HMAGIC2) {
871 			if (base != MAP_FAILED)
872 				munmap((caddr_t)base, hsize);
873 			hist_finish();
874 			unlink(hname);
875 			goto retry;
876 		}
877 		if (hsize > 2) {
878 			lines = hist_count_lines(base+2, hsize-2);
879 			if (lines > histsize) {
880 				/* we need to make the file smaller */
881 				if (hist_shrink(base, hsize))
882 					unlink(hname);
883 				munmap((caddr_t)base, hsize);
884 				hist_finish();
885 				goto retry;
886 			}
887 		}
888 		histload(hist_source, base+2, hsize-2);
889 		munmap((caddr_t)base, hsize);
890 	}
891 	(void) flock(histfd, LOCK_UN);
892 	hsize = lseek(histfd, 0L, SEEK_END);
893 }
894 
895 typedef enum state {
896 	shdr,		/* expecting a header */
897 	sline,		/* looking for a null byte to end the line */
898 	sn1,		/* bytes 1 to 4 of a line no */
899 	sn2, sn3, sn4
900 } State;
901 
902 static int
903 hist_count_lines(base, bytes)
904 	register unsigned char *base;
905 	register int bytes;
906 {
907 	State state = shdr;
908 	int lines = 0;
909 
910 	while (bytes--) {
911 		switch (state)
912 		{
913 		case shdr:
914 			if (*base == COMMAND)
915 				state = sn1;
916 			break;
917 		case sn1:
918 			state = sn2; break;
919 		case sn2:
920 			state = sn3; break;
921 		case sn3:
922 			state = sn4; break;
923 		case sn4:
924 			state = sline; break;
925 		case sline:
926 			if (*base == '\0')
927 				lines++, state = shdr;
928 		}
929 		base++;
930 	}
931 	return lines;
932 }
933 
934 /*
935  *	Shrink the history file to histsize lines
936  */
937 static int
938 hist_shrink(oldbase, oldbytes)
939 	unsigned char *oldbase;
940 	int oldbytes;
941 {
942 	int fd;
943 	char	nfile[1024];
944 	struct	stat statb;
945 	unsigned char *nbase = oldbase;
946 	int nbytes = oldbytes;
947 
948 	nbase = hist_skip_back(nbase, &nbytes, histsize);
949 	if (nbase == NULL)
950 		return 1;
951 	if (nbase == oldbase)
952 		return 0;
953 
954 	/*
955 	 *	create temp file
956 	 */
957 	(void) shf_snprintf(nfile, sizeof(nfile), "%s.%d", hname, procpid);
958 	if ((fd = creat(nfile, 0600)) < 0)
959 		return 1;
960 
961 	if (sprinkle(fd)) {
962 		close(fd);
963 		unlink(nfile);
964 		return 1;
965 	}
966 	if (write(fd, nbase, nbytes) != nbytes) {
967 		close(fd);
968 		unlink(nfile);
969 		return 1;
970 	}
971 	/*
972 	 *	worry about who owns this file
973 	 */
974 	if (fstat(histfd, &statb) >= 0)
975 		fchown(fd, statb.st_uid, statb.st_gid);
976 	close(fd);
977 
978 	/*
979 	 *	rename
980 	 */
981 	if (rename(nfile, hname) < 0)
982 		return 1;
983 	return 0;
984 }
985 
986 
987 /*
988  *	find a pointer to the data `no' back from the end of the file
989  *	return the pointer and the number of bytes left
990  */
991 static unsigned char *
992 hist_skip_back(base, bytes, no)
993 	unsigned char *base;
994 	int *bytes;
995 	int no;
996 {
997 	register int lines = 0;
998 	register unsigned char *ep;
999 
1000 	for (ep = base + *bytes; --ep > base; ) {
1001 		/* this doesn't really work: the 4 byte line number that is
1002 		 * encoded after the COMMAND byte can itself contain the
1003 		 * COMMAND byte....
1004 		 */
1005 		for (; ep > base && *ep != COMMAND; ep--)
1006 			;
1007 		if (ep == base)
1008 			break;
1009 		if (++lines == no) {
1010 			*bytes = *bytes - ((char *)ep - (char *)base);
1011 			return ep;
1012 		}
1013 	}
1014 	return NULL;
1015 }
1016 
1017 /*
1018  *	load the history structure from the stored data
1019  */
1020 static void
1021 histload(s, base, bytes)
1022 	Source *s;
1023 	register unsigned char *base;
1024 	register int bytes;
1025 {
1026 	State state;
1027 	int	lno = 0;
1028 	unsigned char	*line = NULL;
1029 
1030 	for (state = shdr; bytes-- > 0; base++) {
1031 		switch (state) {
1032 		case shdr:
1033 			if (*base == COMMAND)
1034 				state = sn1;
1035 			break;
1036 		case sn1:
1037 			lno = (((*base)&0xff)<<24);
1038 			state = sn2;
1039 			break;
1040 		case sn2:
1041 			lno |= (((*base)&0xff)<<16);
1042 			state = sn3;
1043 			break;
1044 		case sn3:
1045 			lno |= (((*base)&0xff)<<8);
1046 			state = sn4;
1047 			break;
1048 		case sn4:
1049 			lno |= (*base)&0xff;
1050 			line = base+1;
1051 			state = sline;
1052 			break;
1053 		case sline:
1054 			if (*base == '\0') {
1055 				/* worry about line numbers */
1056 				if (histptr >= histlist && lno-1 != s->line) {
1057 					/* a replacement ? */
1058 					histinsert(s, lno, line);
1059 				}
1060 				else {
1061 					s->line = lno;
1062 					histsave(lno, (char *)line, 0);
1063 				}
1064 				state = shdr;
1065 			}
1066 		}
1067 	}
1068 }
1069 
1070 /*
1071  *	Insert a line into the history at a specified number
1072  */
1073 static void
1074 histinsert(s, lno, line)
1075 	Source *s;
1076 	int lno;
1077 	unsigned char *line;
1078 {
1079 	register char **hp;
1080 
1081 	if (lno >= s->line-(histptr-histlist) && lno <= s->line) {
1082 		hp = &histptr[lno-s->line];
1083 		if (*hp)
1084 			afree((void*)*hp, APERM);
1085 		*hp = str_save((char *)line, APERM);
1086 	}
1087 }
1088 
1089 /*
1090  *	write a command to the end of the history file
1091  *	This *MAY* seem easy but it's also necessary to check
1092  *	that the history file has not changed in size.
1093  *	If it has - then some other shell has written to it
1094  *	and we should read those commands to update our history
1095  */
1096 static void
1097 writehistfile(lno, cmd)
1098 	int lno;
1099 	char *cmd;
1100 {
1101 	int	sizenow;
1102 	unsigned char	*base;
1103 	unsigned char	*new;
1104 	int	bytes;
1105 	unsigned char	hdr[5];
1106 
1107 	(void) flock(histfd, LOCK_EX);
1108 	sizenow = lseek(histfd, 0L, SEEK_END);
1109 	if (sizenow != hsize) {
1110 		/*
1111 		 *	Things have changed
1112 		 */
1113 		if (sizenow > hsize) {
1114 			/* someone has added some lines */
1115 			bytes = sizenow - hsize;
1116 			base = (unsigned char *)mmap(0, sizenow, PROT_READ, MAP_FLAGS, histfd, 0);
1117 			if (base == MAP_FAILED)
1118 				goto bad;
1119 			new = base + hsize;
1120 			if (*new != COMMAND) {
1121 				munmap((caddr_t)base, sizenow);
1122 				goto bad;
1123 			}
1124 			hist_source->line--;
1125 			histload(hist_source, new, bytes);
1126 			hist_source->line++;
1127 			lno = hist_source->line;
1128 			munmap((caddr_t)base, sizenow);
1129 			hsize = sizenow;
1130 		} else {
1131 			/* it has shrunk */
1132 			/* but to what? */
1133 			/* we'll give up for now */
1134 			goto bad;
1135 		}
1136 	}
1137 	/*
1138 	 *	we can write our bit now
1139 	 */
1140 	hdr[0] = COMMAND;
1141 	hdr[1] = (lno>>24)&0xff;
1142 	hdr[2] = (lno>>16)&0xff;
1143 	hdr[3] = (lno>>8)&0xff;
1144 	hdr[4] = lno&0xff;
1145 	(void) write(histfd, hdr, 5);
1146 	(void) write(histfd, cmd, strlen(cmd)+1);
1147 	hsize = lseek(histfd, 0L, SEEK_END);
1148 	(void) flock(histfd, LOCK_UN);
1149 	return;
1150 bad:
1151 	hist_finish();
1152 }
1153 
1154 void
1155 hist_finish()
1156 {
1157 	(void) flock(histfd, LOCK_UN);
1158 	(void) close(histfd);
1159 	histfd = 0;
1160 }
1161 
1162 /*
1163  *	add magic to the history file
1164  */
1165 static int
1166 sprinkle(fd)
1167 	int fd;
1168 {
1169 	static unsigned char mag[] = { HMAGIC1, HMAGIC2 };
1170 
1171 	return(write(fd, mag, 2) != 2);
1172 }
1173 
1174 # endif
1175 #else /* HISTORY */
1176 
1177 /* No history to be compiled in: dummy routines to avoid lots more ifdefs */
1178 void
1179 init_histvec()
1180 {
1181 }
1182 void
1183 hist_init(s)
1184 	Source *s;
1185 {
1186 }
1187 void
1188 hist_finish()
1189 {
1190 }
1191 void
1192 histsave(lno, cmd, dowrite)
1193 	int lno;
1194 	const char *cmd;
1195 	int dowrite;
1196 {
1197 	errorf("history not enabled");
1198 }
1199 #endif /* HISTORY */
1200