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