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