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