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