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