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