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