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