xref: /openbsd/bin/ksh/history.c (revision d415bd75)
1 /*	$OpenBSD: history.c,v 1.84 2019/10/27 15:02:19 jca Exp $	*/
2 
3 /*
4  * command history
5  */
6 
7 /*
8  *	This file contains
9  *	a)	the original in-memory history  mechanism
10  *	b)	a more complicated mechanism done by  pc@hillside.co.uk
11  *		that more closely follows the real ksh way of doing
12  *		things.
13  */
14 
15 #include <sys/stat.h>
16 
17 #include <errno.h>
18 #include <fcntl.h>
19 #include <stdlib.h>
20 #include <stdio.h>
21 #include <string.h>
22 #include <unistd.h>
23 #include <vis.h>
24 
25 #include "sh.h"
26 
27 static void	history_write(void);
28 static FILE	*history_open(void);
29 static void	history_load(Source *);
30 static void	history_close(void);
31 
32 static int	hist_execute(char *);
33 static int	hist_replace(char **, const char *, const char *, int);
34 static char   **hist_get(const char *, int, int);
35 static char   **hist_get_oldest(void);
36 static void	histbackup(void);
37 
38 static FILE	*histfh;
39 static char   **histbase;	/* actual start of the history[] allocation */
40 static char   **current;	/* current position in history[] */
41 static char    *hname;		/* current name of history file */
42 static int	hstarted;	/* set after hist_init() called */
43 static int	ignoredups;	/* ditch duplicated history lines? */
44 static int	ignorespace;	/* ditch lines starting with a space? */
45 static Source	*hist_source;
46 static uint32_t	line_co;
47 
48 static struct stat last_sb;
49 
50 static volatile sig_atomic_t	c_fc_depth;
51 
52 int
53 c_fc(char **wp)
54 {
55 	struct shf *shf;
56 	struct temp *tf = NULL;
57 	char *p, *editor = NULL;
58 	int gflag = 0, lflag = 0, nflag = 0, sflag = 0, rflag = 0;
59 	int optc, ret;
60 	char *first = NULL, *last = NULL;
61 	char **hfirst, **hlast, **hp;
62 
63 	if (c_fc_depth != 0) {
64 		bi_errorf("history function called recursively");
65 		return 1;
66 	}
67 
68 	if (!Flag(FTALKING_I)) {
69 		bi_errorf("history functions not available");
70 		return 1;
71 	}
72 
73 	while ((optc = ksh_getopt(wp, &builtin_opt,
74 	    "e:glnrs0,1,2,3,4,5,6,7,8,9,")) != -1)
75 		switch (optc) {
76 		case 'e':
77 			p = builtin_opt.optarg;
78 			if (strcmp(p, "-") == 0)
79 				sflag++;
80 			else {
81 				size_t len = strlen(p) + 4;
82 				editor = str_nsave(p, len, ATEMP);
83 				strlcat(editor, " $_", len);
84 			}
85 			break;
86 		case 'g': /* non-at&t ksh */
87 			gflag++;
88 			break;
89 		case 'l':
90 			lflag++;
91 			break;
92 		case 'n':
93 			nflag++;
94 			break;
95 		case 'r':
96 			rflag++;
97 			break;
98 		case 's':	/* posix version of -e - */
99 			sflag++;
100 			break;
101 		  /* kludge city - accept -num as -- -num (kind of) */
102 		case '0': case '1': case '2': case '3': case '4':
103 		case '5': case '6': case '7': case '8': case '9':
104 			p = shf_smprintf("-%c%s",
105 					optc, builtin_opt.optarg);
106 			if (!first)
107 				first = p;
108 			else if (!last)
109 				last = p;
110 			else {
111 				bi_errorf("too many arguments");
112 				return 1;
113 			}
114 			break;
115 		case '?':
116 			return 1;
117 		}
118 	wp += builtin_opt.optind;
119 
120 	/* Substitute and execute command */
121 	if (sflag) {
122 		char *pat = NULL, *rep = NULL;
123 
124 		if (editor || lflag || nflag || rflag) {
125 			bi_errorf("can't use -e, -l, -n, -r with -s (-e -)");
126 			return 1;
127 		}
128 
129 		/* Check for pattern replacement argument */
130 		if (*wp && **wp && (p = strchr(*wp + 1, '='))) {
131 			pat = str_save(*wp, ATEMP);
132 			p = pat + (p - *wp);
133 			*p++ = '\0';
134 			rep = p;
135 			wp++;
136 		}
137 		/* Check for search prefix */
138 		if (!first && (first = *wp))
139 			wp++;
140 		if (last || *wp) {
141 			bi_errorf("too many arguments");
142 			return 1;
143 		}
144 
145 		hp = first ? hist_get(first, false, false) :
146 		    hist_get_newest(false);
147 		if (!hp)
148 			return 1;
149 		c_fc_depth++;
150 		ret = hist_replace(hp, pat, rep, gflag);
151 		c_fc_reset();
152 		return ret;
153 	}
154 
155 	if (editor && (lflag || nflag)) {
156 		bi_errorf("can't use -l, -n with -e");
157 		return 1;
158 	}
159 
160 	if (!first && (first = *wp))
161 		wp++;
162 	if (!last && (last = *wp))
163 		wp++;
164 	if (*wp) {
165 		bi_errorf("too many arguments");
166 		return 1;
167 	}
168 	if (!first) {
169 		hfirst = lflag ? hist_get("-16", true, true) :
170 		    hist_get_newest(false);
171 		if (!hfirst)
172 			return 1;
173 		/* can't fail if hfirst didn't fail */
174 		hlast = hist_get_newest(false);
175 	} else {
176 		/* POSIX says not an error if first/last out of bounds
177 		 * when range is specified; at&t ksh and pdksh allow out of
178 		 * bounds for -l as well.
179 		 */
180 		hfirst = hist_get(first, (lflag || last) ? true : false,
181 		    lflag ? true : false);
182 		if (!hfirst)
183 			return 1;
184 		hlast = last ? hist_get(last, true, lflag ? true : false) :
185 		    (lflag ? hist_get_newest(false) : hfirst);
186 		if (!hlast)
187 			return 1;
188 	}
189 	if (hfirst > hlast) {
190 		char **temp;
191 
192 		temp = hfirst; hfirst = hlast; hlast = temp;
193 		rflag = !rflag; /* POSIX */
194 	}
195 
196 	/* List history */
197 	if (lflag) {
198 		char *s, *t;
199 		const char *nfmt = nflag ? "\t" : "%d\t";
200 
201 		for (hp = rflag ? hlast : hfirst;
202 		    hp >= hfirst && hp <= hlast; hp += rflag ? -1 : 1) {
203 			shf_fprintf(shl_stdout, nfmt,
204 			    hist_source->line - (int) (histptr - hp));
205 			/* print multi-line commands correctly */
206 			for (s = *hp; (t = strchr(s, '\n')); s = t)
207 				shf_fprintf(shl_stdout, "%.*s\t", ++t - s, s);
208 			shf_fprintf(shl_stdout, "%s\n", s);
209 		}
210 		shf_flush(shl_stdout);
211 		return 0;
212 	}
213 
214 	/* Run editor on selected lines, then run resulting commands */
215 
216 	tf = maketemp(ATEMP, TT_HIST_EDIT, &genv->temps);
217 	if (!(shf = tf->shf)) {
218 		bi_errorf("cannot create temp file %s - %s",
219 		    tf->name, strerror(errno));
220 		return 1;
221 	}
222 	for (hp = rflag ? hlast : hfirst;
223 	    hp >= hfirst && hp <= hlast; hp += rflag ? -1 : 1)
224 		shf_fprintf(shf, "%s\n", *hp);
225 	if (shf_close(shf) == EOF) {
226 		bi_errorf("error writing temporary file - %s", strerror(errno));
227 		return 1;
228 	}
229 
230 	/* Ignore setstr errors here (arbitrary) */
231 	setstr(local("_", false), tf->name, KSH_RETURN_ERROR);
232 
233 	/* XXX: source should not get trashed by this.. */
234 	{
235 		Source *sold = source;
236 
237 		ret = command(editor ? editor : "${FCEDIT:-/bin/ed} $_", 0);
238 		source = sold;
239 		if (ret)
240 			return ret;
241 	}
242 
243 	{
244 		struct stat statb;
245 		XString xs;
246 		char *xp;
247 		int n;
248 
249 		if (!(shf = shf_open(tf->name, O_RDONLY, 0, 0))) {
250 			bi_errorf("cannot open temp file %s", tf->name);
251 			return 1;
252 		}
253 
254 		n = fstat(shf->fd, &statb) == -1 ? 128 :
255 		    statb.st_size + 1;
256 		Xinit(xs, xp, n, hist_source->areap);
257 		while ((n = shf_read(xp, Xnleft(xs, xp), shf)) > 0) {
258 			xp += n;
259 			if (Xnleft(xs, xp) <= 0)
260 				XcheckN(xs, xp, Xlength(xs, xp));
261 		}
262 		if (n < 0) {
263 			bi_errorf("error reading temp file %s - %s",
264 			    tf->name, strerror(shf->errno_));
265 			shf_close(shf);
266 			return 1;
267 		}
268 		shf_close(shf);
269 		*xp = '\0';
270 		strip_nuls(Xstring(xs, xp), Xlength(xs, xp));
271 		c_fc_depth++;
272 		ret = hist_execute(Xstring(xs, xp));
273 		c_fc_reset();
274 		return ret;
275 	}
276 }
277 
278 /* Reset the c_fc depth counter.
279  * Made available for when an fc call is interrupted.
280  */
281 void
282 c_fc_reset(void)
283 {
284 	c_fc_depth = 0;
285 }
286 
287 /* Save cmd in history, execute cmd (cmd gets trashed) */
288 static int
289 hist_execute(char *cmd)
290 {
291 	Source *sold;
292 	int ret;
293 	char *p, *q;
294 
295 	histbackup();
296 
297 	for (p = cmd; p; p = q) {
298 		if ((q = strchr(p, '\n'))) {
299 			*q++ = '\0'; /* kill the newline */
300 			if (!*q) /* ignore trailing newline */
301 				q = NULL;
302 		}
303 		histsave(++(hist_source->line), p, 1);
304 
305 		shellf("%s\n", p); /* POSIX doesn't say this is done... */
306 		if ((p = q)) /* restore \n (trailing \n not restored) */
307 			q[-1] = '\n';
308 	}
309 
310 	/* Commands are executed here instead of pushing them onto the
311 	 * input 'cause posix says the redirection and variable assignments
312 	 * in
313 	 *	X=y fc -e - 42 2> /dev/null
314 	 * are to effect the repeated commands environment.
315 	 */
316 	/* XXX: source should not get trashed by this.. */
317 	sold = source;
318 	ret = command(cmd, 0);
319 	source = sold;
320 	return ret;
321 }
322 
323 static int
324 hist_replace(char **hp, const char *pat, const char *rep, int global)
325 {
326 	char *line;
327 
328 	if (!pat)
329 		line = str_save(*hp, ATEMP);
330 	else {
331 		char *s, *s1;
332 		int pat_len = strlen(pat);
333 		int rep_len = strlen(rep);
334 		int len;
335 		XString xs;
336 		char *xp;
337 		int any_subst = 0;
338 
339 		Xinit(xs, xp, 128, ATEMP);
340 		for (s = *hp; (s1 = strstr(s, pat)) && (!any_subst || global);
341 		    s = s1 + pat_len) {
342 			any_subst = 1;
343 			len = s1 - s;
344 			XcheckN(xs, xp, len + rep_len);
345 			memcpy(xp, s, len);		/* first part */
346 			xp += len;
347 			memcpy(xp, rep, rep_len);	/* replacement */
348 			xp += rep_len;
349 		}
350 		if (!any_subst) {
351 			bi_errorf("substitution failed");
352 			return 1;
353 		}
354 		len = strlen(s) + 1;
355 		XcheckN(xs, xp, len);
356 		memcpy(xp, s, len);
357 		xp += len;
358 		line = Xclose(xs, xp);
359 	}
360 	return hist_execute(line);
361 }
362 
363 /*
364  * get pointer to history given pattern
365  * pattern is a number or string
366  */
367 static char **
368 hist_get(const char *str, int approx, int allow_cur)
369 {
370 	char **hp = NULL;
371 	int n;
372 
373 	if (getn(str, &n)) {
374 		hp = histptr + (n < 0 ? n : (n - hist_source->line));
375 		if ((long)hp < (long)history) {
376 			if (approx)
377 				hp = hist_get_oldest();
378 			else {
379 				bi_errorf("%s: not in history", str);
380 				hp = NULL;
381 			}
382 		} else if (hp > histptr) {
383 			if (approx)
384 				hp = hist_get_newest(allow_cur);
385 			else {
386 				bi_errorf("%s: not in history", str);
387 				hp = NULL;
388 			}
389 		} else if (!allow_cur && hp == histptr) {
390 			bi_errorf("%s: invalid range", str);
391 			hp = NULL;
392 		}
393 	} else {
394 		int anchored = *str == '?' ? (++str, 0) : 1;
395 
396 		/* the -1 is to avoid the current fc command */
397 		n = findhist(histptr - history - 1, 0, str, anchored);
398 		if (n < 0) {
399 			bi_errorf("%s: not in history", str);
400 			hp = NULL;
401 		} else
402 			hp = &history[n];
403 	}
404 	return hp;
405 }
406 
407 /* Return a pointer to the newest command in the history */
408 char **
409 hist_get_newest(int allow_cur)
410 {
411 	if (histptr < history || (!allow_cur && histptr == history)) {
412 		bi_errorf("no history (yet)");
413 		return NULL;
414 	}
415 	if (allow_cur)
416 		return histptr;
417 	return histptr - 1;
418 }
419 
420 /* Return a pointer to the oldest command in the history */
421 static char **
422 hist_get_oldest(void)
423 {
424 	if (histptr <= history) {
425 		bi_errorf("no history (yet)");
426 		return NULL;
427 	}
428 	return history;
429 }
430 
431 /******************************/
432 /* Back up over last histsave */
433 /******************************/
434 static void
435 histbackup(void)
436 {
437 	static int last_line = -1;
438 
439 	if (histptr >= history && last_line != hist_source->line) {
440 		hist_source->line--;
441 		afree(*histptr, APERM);
442 		histptr--;
443 		last_line = hist_source->line;
444 	}
445 }
446 
447 static void
448 histreset(void)
449 {
450 	char **hp;
451 
452 	for (hp = history; hp <= histptr; hp++)
453 		afree(*hp, APERM);
454 
455 	histptr = history - 1;
456 	hist_source->line = 0;
457 }
458 
459 /*
460  * Return the current position.
461  */
462 char **
463 histpos(void)
464 {
465 	return current;
466 }
467 
468 int
469 histnum(int n)
470 {
471 	int	last = histptr - history;
472 
473 	if (n < 0 || n >= last) {
474 		current = histptr;
475 		return last;
476 	} else {
477 		current = &history[n];
478 		return n;
479 	}
480 }
481 
482 /*
483  * This will become unnecessary if hist_get is modified to allow
484  * searching from positions other than the end, and in either
485  * direction.
486  */
487 int
488 findhist(int start, int fwd, const char *str, int anchored)
489 {
490 	char	**hp;
491 	int	maxhist = histptr - history;
492 	int	incr = fwd ? 1 : -1;
493 	int	len = strlen(str);
494 
495 	if (start < 0 || start >= maxhist)
496 		start = maxhist;
497 
498 	hp = &history[start];
499 	for (; hp >= history && hp <= histptr; hp += incr)
500 		if ((anchored && strncmp(*hp, str, len) == 0) ||
501 		    (!anchored && strstr(*hp, str)))
502 			return hp - history;
503 
504 	return -1;
505 }
506 
507 int
508 findhistrel(const char *str)
509 {
510 	int	maxhist = histptr - history;
511 	int	start = maxhist - 1;
512 	int	rec = atoi(str);
513 
514 	if (rec == 0)
515 		return -1;
516 	if (rec > 0) {
517 		if (rec > maxhist)
518 			return -1;
519 		return rec - 1;
520 	}
521 	if (rec > maxhist)
522 		return -1;
523 	return start + rec + 1;
524 }
525 
526 void
527 sethistcontrol(const char *str)
528 {
529 	char *spec, *tok, *state;
530 
531 	ignorespace = 0;
532 	ignoredups = 0;
533 
534 	if (str == NULL)
535 		return;
536 
537 	spec = str_save(str, ATEMP);
538 	for (tok = strtok_r(spec, ":", &state); tok != NULL;
539 	     tok = strtok_r(NULL, ":", &state)) {
540 		if (strcmp(tok, "ignoredups") == 0)
541 			ignoredups = 1;
542 		else if (strcmp(tok, "ignorespace") == 0)
543 			ignorespace = 1;
544 	}
545 	afree(spec, ATEMP);
546 }
547 
548 /*
549  *	set history
550  *	this means reallocating the dataspace
551  */
552 void
553 sethistsize(int n)
554 {
555 	if (n > 0 && (uint32_t)n != histsize) {
556 		char **tmp;
557 		int offset = histptr - history;
558 
559 		/* save most recent history */
560 		if (offset > n - 1) {
561 			char **hp;
562 
563 			offset = n - 1;
564 			for (hp = history; hp < histptr - offset; hp++)
565 				afree(*hp, APERM);
566 			memmove(history, histptr - offset, n * sizeof(char *));
567 		}
568 
569 		tmp = reallocarray(histbase, n + 1, sizeof(char *));
570 		if (tmp != NULL) {
571 			histbase = tmp;
572 			histsize = n;
573 			history = histbase + 1;
574 			histptr = history + offset;
575 		} else
576 			warningf(false, "resizing history storage: %s",
577 			    strerror(errno));
578 	}
579 }
580 
581 /*
582  *	set history file
583  *	This can mean reloading/resetting/starting history file
584  *	maintenance
585  */
586 void
587 sethistfile(const char *name)
588 {
589 	/* if not started then nothing to do */
590 	if (hstarted == 0)
591 		return;
592 
593 	/* if the name is the same as the name we have */
594 	if (hname && strcmp(hname, name) == 0)
595 		return;
596 	/*
597 	 * its a new name - possibly
598 	 */
599 	if (hname) {
600 		afree(hname, APERM);
601 		hname = NULL;
602 		histreset();
603 	}
604 
605 	history_close();
606 	hist_init(hist_source);
607 }
608 
609 /*
610  *	initialise the history vector
611  */
612 void
613 init_histvec(void)
614 {
615 	if (histbase == NULL) {
616 		histsize = HISTORYSIZE;
617 		/*
618 		 * allocate one extra element so that histptr always
619 		 * lies within array bounds
620 		 */
621 		histbase = reallocarray(NULL, histsize + 1, sizeof(char *));
622 		if (histbase == NULL)
623 			internal_errorf("allocating history storage: %s",
624 			    strerror(errno));
625 		*histbase = NULL;
626 		history = histbase + 1;
627 		histptr = history - 1;
628 	}
629 }
630 
631 static void
632 history_lock(int operation)
633 {
634 	while (flock(fileno(histfh), operation) != 0) {
635 		if (errno == EINTR || errno == EAGAIN)
636 			continue;
637 		else
638 			break;
639 	}
640 }
641 
642 /*
643  *	Routines added by Peter Collinson BSDI(Europe)/Hillside Systems to
644  *	a) permit HISTSIZE to control number of lines of history stored
645  *	b) maintain a physical history file
646  *
647  *	It turns out that there is a lot of ghastly hackery here
648  */
649 
650 
651 /*
652  * save command in history
653  */
654 void
655 histsave(int lno, const char *cmd, int dowrite)
656 {
657 	char		*c, *cp;
658 
659 	if (ignorespace && cmd[0] == ' ')
660 		return;
661 
662 	c = str_save(cmd, APERM);
663 	if ((cp = strrchr(c, '\n')) != NULL)
664 		*cp = '\0';
665 
666 	/*
667 	 * XXX to properly check for duplicated lines we should first reload
668 	 * the histfile if needed
669 	 */
670 	if (ignoredups && histptr >= history && strcmp(*histptr, c) == 0) {
671 		afree(c, APERM);
672 		return;
673 	}
674 
675 	if (dowrite && histfh) {
676 #ifndef SMALL
677 		struct stat	sb;
678 
679 		history_lock(LOCK_EX);
680 		if (fstat(fileno(histfh), &sb) != -1) {
681 			if (timespeccmp(&sb.st_mtim, &last_sb.st_mtim, ==))
682 				; /* file is unchanged */
683 			else {
684 				histreset();
685 				history_load(hist_source);
686 			}
687 		}
688 #endif
689 	}
690 
691 	if (histptr < history + histsize - 1)
692 		histptr++;
693 	else { /* remove oldest command */
694 		afree(*history, APERM);
695 		memmove(history, history + 1,
696 		    (histsize - 1) * sizeof(*history));
697 	}
698 	*histptr = c;
699 
700 	if (dowrite && histfh) {
701 #ifndef SMALL
702 		char *encoded;
703 
704 		/* append to file */
705 		if (fseeko(histfh, 0, SEEK_END) == 0 &&
706 		    stravis(&encoded, c, VIS_SAFE | VIS_NL) != -1) {
707 			fprintf(histfh, "%s\n", encoded);
708 			fflush(histfh);
709 			fstat(fileno(histfh), &last_sb);
710 			line_co++;
711 			history_write();
712 			free(encoded);
713 		}
714 		history_lock(LOCK_UN);
715 #endif
716 	}
717 }
718 
719 static FILE *
720 history_open(void)
721 {
722 	FILE		*f = NULL;
723 #ifndef SMALL
724 	struct stat	sb;
725 	int		fd, fddup;
726 
727 	if ((fd = open(hname, O_RDWR | O_CREAT | O_EXLOCK, 0600)) == -1)
728 		return NULL;
729 	if (fstat(fd, &sb) == -1 || sb.st_uid != getuid()) {
730 		close(fd);
731 		return NULL;
732 	}
733 	fddup = savefd(fd);
734 	if (fddup != fd)
735 		close(fd);
736 
737 	if ((f = fdopen(fddup, "r+")) == NULL)
738 		close(fddup);
739 	else
740 		last_sb = sb;
741 #endif
742 	return f;
743 }
744 
745 static void
746 history_close(void)
747 {
748 	if (histfh) {
749 		fflush(histfh);
750 		fclose(histfh);
751 		histfh = NULL;
752 	}
753 }
754 
755 static void
756 history_load(Source *s)
757 {
758 	char		*p, encoded[LINE + 1], line[LINE + 1];
759 	int		 toolongseen = 0;
760 
761 	rewind(histfh);
762 	line_co = 1;
763 
764 	/* just read it all; will auto resize history upon next command */
765 	while (fgets(encoded, sizeof(encoded), histfh)) {
766 		if ((p = strchr(encoded, '\n')) == NULL) {
767 			/* discard overlong line */
768 			do {
769 				/* maybe a missing trailing newline? */
770 				if (strlen(encoded) != sizeof(encoded) - 1) {
771 					bi_errorf("history file is corrupt");
772 					return;
773 				}
774 			} while (fgets(encoded, sizeof(encoded), histfh)
775 			    && strchr(encoded, '\n') == NULL);
776 
777 			if (!toolongseen) {
778 				toolongseen = 1;
779 				bi_errorf("ignored history line(s) longer than"
780 				    " %d bytes", LINE);
781 			}
782 
783 			continue;
784 		}
785 		*p = '\0';
786 		s->line = line_co;
787 		s->cmd_offset = line_co;
788 		strunvis(line, encoded);
789 		histsave(line_co, line, 0);
790 		line_co++;
791 	}
792 
793 	history_write();
794 }
795 
796 #define HMAGIC1 0xab
797 #define HMAGIC2 0xcd
798 
799 void
800 hist_init(Source *s)
801 {
802 	int oldmagic1, oldmagic2;
803 
804 	if (Flag(FTALKING) == 0)
805 		return;
806 
807 	hstarted = 1;
808 
809 	hist_source = s;
810 
811 	if (str_val(global("HISTFILE")) == null)
812 		return;
813 	hname = str_save(str_val(global("HISTFILE")), APERM);
814 	histfh = history_open();
815 	if (histfh == NULL)
816 		return;
817 
818 	oldmagic1 = fgetc(histfh);
819 	oldmagic2 = fgetc(histfh);
820 
821 	if (oldmagic1 == EOF || oldmagic2 == EOF) {
822 		if (!feof(histfh) && ferror(histfh)) {
823 			history_close();
824 			return;
825 		}
826 	} else if (oldmagic1 == HMAGIC1 && oldmagic2 == HMAGIC2) {
827 		bi_errorf("ignoring old style history file");
828 		history_close();
829 		return;
830 	}
831 
832 	history_load(s);
833 
834 	history_lock(LOCK_UN);
835 }
836 
837 static void
838 history_write(void)
839 {
840 	char		**hp, *encoded;
841 
842 	/* see if file has grown over 25% */
843 	if (line_co < histsize + (histsize / 4))
844 		return;
845 
846 	/* rewrite the whole caboodle */
847 	rewind(histfh);
848 	if (ftruncate(fileno(histfh), 0) == -1) {
849 		bi_errorf("failed to rewrite history file - %s",
850 		    strerror(errno));
851 	}
852 	for (hp = history; hp <= histptr; hp++) {
853 		if (stravis(&encoded, *hp, VIS_SAFE | VIS_NL) != -1) {
854 			if (fprintf(histfh, "%s\n", encoded) == -1) {
855 				free(encoded);
856 				return;
857 			}
858 			free(encoded);
859 		}
860 	}
861 
862 	line_co = histsize;
863 
864 	fflush(histfh);
865 	fstat(fileno(histfh), &last_sb);
866 }
867 
868 void
869 hist_finish(void)
870 {
871 	history_close();
872 }
873