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