Home | History | Annotate | Line # | Download | only in ksh
history.c revision 1.11.26.1
      1 /*	$NetBSD: history.c,v 1.11.26.1 2017/03/20 06:51:32 pgoyette 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.26.1 2017/03/20 06:51:32 pgoyette 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   if ((fd = open(hname, O_WRONLY | O_CREAT | O_TRUNC | O_EXLOCK, 0777)) != -1) {
    761     /* Remove anything written before we got the lock */
    762     ftruncate(fd, 0);
    763     if ((fh = fdopen(fd, "w")) != NULL) {
    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 
    771 # else /* EASY_HISTORY */
    772 
    773 /*
    774  *	Routines added by Peter Collinson BSDI(Europe)/Hillside Systems to
    775  *	a) permit HISTSIZE to control number of lines of history stored
    776  *	b) maintain a physical history file
    777  *
    778  *	It turns out that there is a lot of ghastly hackery here
    779  */
    780 
    781 
    782 /*
    783  * save command in history
    784  */
    785 void
    786 histsave(lno, cmd, dowrite)
    787 	int lno;
    788 	const char *cmd;
    789 	int dowrite;
    790 {
    791 	register char **hp;
    792 	char *c, *cp;
    793 
    794 	c = str_save(cmd, APERM);
    795 	if ((cp = strchr(c, '\n')) != NULL)
    796 		*cp = '\0';
    797 
    798 	if (histfd && dowrite)
    799 		writehistfile(lno, c);
    800 
    801 	hp = histptr;
    802 
    803 	if (++hp >= histlist + histsize) { /* remove oldest command */
    804 		afree((void*)*histlist, APERM);
    805 		for (hp = histlist; hp < histlist + histsize - 1; hp++)
    806 			hp[0] = hp[1];
    807 	}
    808 	*hp = c;
    809 	histptr = hp;
    810 }
    811 
    812 /*
    813  *	Write history data to a file nominated by HISTFILE
    814  *	if HISTFILE is unset then history still happens, but
    815  *	the data is not written to a file
    816  *	All copies of ksh looking at the file will maintain the
    817  *	same history. This is ksh behaviour.
    818  *
    819  *	This stuff uses mmap()
    820  *	if your system ain't got it - then you'll have to undef HISTORYFILE
    821  */
    822 
    823 /*
    824  *	Open a history file
    825  *	Format is:
    826  *	Bytes 1, 2: HMAGIC - just to check that we are dealing with
    827  *		    the correct object
    828  *	Then follows a number of stored commands
    829  *	Each command is
    830  *	<command byte><command number(4 bytes)><bytes><null>
    831  */
    832 # define HMAGIC1		0xab
    833 # define HMAGIC2		0xcd
    834 # define COMMAND		0xff
    835 
    836 void
    837 hist_init(s)
    838 	Source *s;
    839 {
    840 	unsigned char	*base;
    841 	int	lines;
    842 	int	fd;
    843 
    844 	if (Flag(FTALKING) == 0)
    845 		return;
    846 
    847 	hstarted = 1;
    848 
    849 	hist_source = s;
    850 
    851 	hname = str_val(global("HISTFILE"));
    852 	if (hname == NULL)
    853 		return;
    854 	hname = str_save(hname, APERM);
    855 
    856   retry:
    857 	/* we have a file and are interactive */
    858 	if ((fd = open(hname, O_RDWR|O_CREAT|O_APPEND, 0600)) < 0)
    859 		return;
    860 
    861 	histfd = savefd(fd, 0);
    862 
    863 	(void) flock(histfd, LOCK_EX);
    864 
    865 	hsize = lseek(histfd, 0L, SEEK_END);
    866 
    867 	if (hsize == 0) {
    868 		/* add magic */
    869 		if (sprinkle(histfd)) {
    870 			hist_finish();
    871 			return;
    872 		}
    873 	}
    874 	else if (hsize > 0) {
    875 		/*
    876 		 * we have some data
    877 		 */
    878 		base = (unsigned char *)mmap(0, hsize, PROT_READ, MAP_FLAGS, histfd, 0);
    879 		/*
    880 		 * check on its validity
    881 		 */
    882 		if (base == MAP_FAILED || *base != HMAGIC1 || base[1] != HMAGIC2) {
    883 			if (base != MAP_FAILED)
    884 				munmap((caddr_t)base, hsize);
    885 			hist_finish();
    886 			unlink(hname);
    887 			goto retry;
    888 		}
    889 		if (hsize > 2) {
    890 			lines = hist_count_lines(base+2, hsize-2);
    891 			if (lines > histsize) {
    892 				/* we need to make the file smaller */
    893 				if (hist_shrink(base, hsize))
    894 					unlink(hname);
    895 				munmap((caddr_t)base, hsize);
    896 				hist_finish();
    897 				goto retry;
    898 			}
    899 		}
    900 		histload(hist_source, base+2, hsize-2);
    901 		munmap((caddr_t)base, hsize);
    902 	}
    903 	(void) flock(histfd, LOCK_UN);
    904 	hsize = lseek(histfd, 0L, SEEK_END);
    905 }
    906 
    907 typedef enum state {
    908 	shdr,		/* expecting a header */
    909 	sline,		/* looking for a null byte to end the line */
    910 	sn1,		/* bytes 1 to 4 of a line no */
    911 	sn2, sn3, sn4
    912 } State;
    913 
    914 static int
    915 hist_count_lines(base, bytes)
    916 	register unsigned char *base;
    917 	register int bytes;
    918 {
    919 	State state = shdr;
    920 	int lines = 0;
    921 
    922 	while (bytes--) {
    923 		switch (state)
    924 		{
    925 		case shdr:
    926 			if (*base == COMMAND)
    927 				state = sn1;
    928 			break;
    929 		case sn1:
    930 			state = sn2; break;
    931 		case sn2:
    932 			state = sn3; break;
    933 		case sn3:
    934 			state = sn4; break;
    935 		case sn4:
    936 			state = sline; break;
    937 		case sline:
    938 			if (*base == '\0')
    939 				lines++, state = shdr;
    940 		}
    941 		base++;
    942 	}
    943 	return lines;
    944 }
    945 
    946 /*
    947  *	Shrink the history file to histsize lines
    948  */
    949 static int
    950 hist_shrink(oldbase, oldbytes)
    951 	unsigned char *oldbase;
    952 	int oldbytes;
    953 {
    954 	int fd;
    955 	char	nfile[1024];
    956 	struct	stat statb;
    957 	unsigned char *nbase = oldbase;
    958 	int nbytes = oldbytes;
    959 
    960 	nbase = hist_skip_back(nbase, &nbytes, histsize);
    961 	if (nbase == NULL)
    962 		return 1;
    963 	if (nbase == oldbase)
    964 		return 0;
    965 
    966 	/*
    967 	 *	create temp file
    968 	 */
    969 	(void) shf_snprintf(nfile, sizeof(nfile), "%s.%d", hname, procpid);
    970 	if ((fd = creat(nfile, 0600)) < 0)
    971 		return 1;
    972 
    973 	if (sprinkle(fd)) {
    974 		close(fd);
    975 		unlink(nfile);
    976 		return 1;
    977 	}
    978 	if (write(fd, nbase, nbytes) != nbytes) {
    979 		close(fd);
    980 		unlink(nfile);
    981 		return 1;
    982 	}
    983 	/*
    984 	 *	worry about who owns this file
    985 	 */
    986 	if (fstat(histfd, &statb) >= 0)
    987 		fchown(fd, statb.st_uid, statb.st_gid);
    988 	close(fd);
    989 
    990 	/*
    991 	 *	rename
    992 	 */
    993 	if (rename(nfile, hname) < 0)
    994 		return 1;
    995 	return 0;
    996 }
    997 
    998 
    999 /*
   1000  *	find a pointer to the data `no' back from the end of the file
   1001  *	return the pointer and the number of bytes left
   1002  */
   1003 static unsigned char *
   1004 hist_skip_back(base, bytes, no)
   1005 	unsigned char *base;
   1006 	int *bytes;
   1007 	int no;
   1008 {
   1009 	register int lines = 0;
   1010 	register unsigned char *ep;
   1011 
   1012 	for (ep = base + *bytes; --ep > base; ) {
   1013 		/* this doesn't really work: the 4 byte line number that is
   1014 		 * encoded after the COMMAND byte can itself contain the
   1015 		 * COMMAND byte....
   1016 		 */
   1017 		for (; ep > base && *ep != COMMAND; ep--)
   1018 			;
   1019 		if (ep == base)
   1020 			break;
   1021 		if (++lines == no) {
   1022 			*bytes = *bytes - ((char *)ep - (char *)base);
   1023 			return ep;
   1024 		}
   1025 	}
   1026 	return NULL;
   1027 }
   1028 
   1029 /*
   1030  *	load the history structure from the stored data
   1031  */
   1032 static void
   1033 histload(s, base, bytes)
   1034 	Source *s;
   1035 	register unsigned char *base;
   1036 	register int bytes;
   1037 {
   1038 	State state;
   1039 	int	lno = 0;
   1040 	unsigned char	*line = NULL;
   1041 
   1042 	for (state = shdr; bytes-- > 0; base++) {
   1043 		switch (state) {
   1044 		case shdr:
   1045 			if (*base == COMMAND)
   1046 				state = sn1;
   1047 			break;
   1048 		case sn1:
   1049 			lno = (((*base)&0xff)<<24);
   1050 			state = sn2;
   1051 			break;
   1052 		case sn2:
   1053 			lno |= (((*base)&0xff)<<16);
   1054 			state = sn3;
   1055 			break;
   1056 		case sn3:
   1057 			lno |= (((*base)&0xff)<<8);
   1058 			state = sn4;
   1059 			break;
   1060 		case sn4:
   1061 			lno |= (*base)&0xff;
   1062 			line = base+1;
   1063 			state = sline;
   1064 			break;
   1065 		case sline:
   1066 			if (*base == '\0') {
   1067 				/* worry about line numbers */
   1068 				if (histptr >= histlist && lno-1 != s->line) {
   1069 					/* a replacement ? */
   1070 					histinsert(s, lno, line);
   1071 				}
   1072 				else {
   1073 					s->line = lno;
   1074 					histsave(lno, (char *)line, 0);
   1075 				}
   1076 				state = shdr;
   1077 			}
   1078 		}
   1079 	}
   1080 }
   1081 
   1082 /*
   1083  *	Insert a line into the history at a specified number
   1084  */
   1085 static void
   1086 histinsert(s, lno, line)
   1087 	Source *s;
   1088 	int lno;
   1089 	unsigned char *line;
   1090 {
   1091 	register char **hp;
   1092 
   1093 	if (lno >= s->line-(histptr-histlist) && lno <= s->line) {
   1094 		hp = &histptr[lno-s->line];
   1095 		if (*hp)
   1096 			afree((void*)*hp, APERM);
   1097 		*hp = str_save((char *)line, APERM);
   1098 	}
   1099 }
   1100 
   1101 /*
   1102  *	write a command to the end of the history file
   1103  *	This *MAY* seem easy but it's also necessary to check
   1104  *	that the history file has not changed in size.
   1105  *	If it has - then some other shell has written to it
   1106  *	and we should read those commands to update our history
   1107  */
   1108 static void
   1109 writehistfile(lno, cmd)
   1110 	int lno;
   1111 	char *cmd;
   1112 {
   1113 	int	sizenow;
   1114 	unsigned char	*base;
   1115 	unsigned char	*new;
   1116 	int	bytes;
   1117 	unsigned char	hdr[5];
   1118 
   1119 	(void) flock(histfd, LOCK_EX);
   1120 	sizenow = lseek(histfd, 0L, SEEK_END);
   1121 	if (sizenow != hsize) {
   1122 		/*
   1123 		 *	Things have changed
   1124 		 */
   1125 		if (sizenow > hsize) {
   1126 			/* someone has added some lines */
   1127 			bytes = sizenow - hsize;
   1128 			base = (unsigned char *)mmap(0, sizenow, PROT_READ, MAP_FLAGS, histfd, 0);
   1129 			if (base == MAP_FAILED)
   1130 				goto bad;
   1131 			new = base + hsize;
   1132 			if (*new != COMMAND) {
   1133 				munmap((caddr_t)base, sizenow);
   1134 				goto bad;
   1135 			}
   1136 			hist_source->line--;
   1137 			histload(hist_source, new, bytes);
   1138 			hist_source->line++;
   1139 			lno = hist_source->line;
   1140 			munmap((caddr_t)base, sizenow);
   1141 			hsize = sizenow;
   1142 		} else {
   1143 			/* it has shrunk */
   1144 			/* but to what? */
   1145 			/* we'll give up for now */
   1146 			goto bad;
   1147 		}
   1148 	}
   1149 	/*
   1150 	 *	we can write our bit now
   1151 	 */
   1152 	hdr[0] = COMMAND;
   1153 	hdr[1] = (lno>>24)&0xff;
   1154 	hdr[2] = (lno>>16)&0xff;
   1155 	hdr[3] = (lno>>8)&0xff;
   1156 	hdr[4] = lno&0xff;
   1157 	(void) write(histfd, hdr, 5);
   1158 	(void) write(histfd, cmd, strlen(cmd)+1);
   1159 	hsize = lseek(histfd, 0L, SEEK_END);
   1160 	(void) flock(histfd, LOCK_UN);
   1161 	return;
   1162 bad:
   1163 	hist_finish();
   1164 }
   1165 
   1166 void
   1167 hist_finish()
   1168 {
   1169 	(void) flock(histfd, LOCK_UN);
   1170 	(void) close(histfd);
   1171 	histfd = 0;
   1172 }
   1173 
   1174 /*
   1175  *	add magic to the history file
   1176  */
   1177 static int
   1178 sprinkle(fd)
   1179 	int fd;
   1180 {
   1181 	static unsigned char mag[] = { HMAGIC1, HMAGIC2 };
   1182 
   1183 	return(write(fd, mag, 2) != 2);
   1184 }
   1185 
   1186 # endif
   1187 #else /* HISTORY */
   1188 
   1189 /* No history to be compiled in: dummy routines to avoid lots more ifdefs */
   1190 void
   1191 init_histvec()
   1192 {
   1193 }
   1194 void
   1195 hist_init(s)
   1196 	Source *s;
   1197 {
   1198 }
   1199 void
   1200 hist_finish()
   1201 {
   1202 }
   1203 void
   1204 histsave(lno, cmd, dowrite)
   1205 	int lno;
   1206 	const char *cmd;
   1207 	int dowrite;
   1208 {
   1209 	errorf("history not enabled");
   1210 }
   1211 #endif /* HISTORY */
   1212