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