xref: /openbsd/bin/ksh/history.c (revision 8c6999ca)
1 /*	$OpenBSD: history.c,v 1.86 2024/08/27 19:27:19 op 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
c_fc(char ** wp)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
c_fc_reset(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
hist_execute(char * cmd)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
hist_replace(char ** hp,const char * pat,const char * rep,int global)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 **
hist_get(const char * str,int approx,int allow_cur)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 **
hist_get_newest(int allow_cur)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 **
hist_get_oldest(void)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
histbackup(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
histreset(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 **
histpos(void)463 histpos(void)
464 {
465 	return current;
466 }
467 
468 int
histnum(int n)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
findhist(int start,int fwd,const char * str,int anchored)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
findhistrel(const char * str)508 findhistrel(const char *str)
509 {
510 	const char *errstr;
511 	int	maxhist = histptr - history;
512 	int	rec;
513 
514 	rec = strtonum(str, -maxhist, maxhist, &errstr);
515 	if (errstr)
516 		return -1;
517 
518 	if (rec == 0)
519 		return -1;
520 	if (rec > 0)
521 		return rec - 1;
522 	return maxhist + rec;
523 }
524 
525 void
sethistcontrol(const char * str)526 sethistcontrol(const char *str)
527 {
528 	char *spec, *tok, *state;
529 
530 	ignorespace = 0;
531 	ignoredups = 0;
532 
533 	if (str == NULL)
534 		return;
535 
536 	spec = str_save(str, ATEMP);
537 	for (tok = strtok_r(spec, ":", &state); tok != NULL;
538 	     tok = strtok_r(NULL, ":", &state)) {
539 		if (strcmp(tok, "ignoredups") == 0)
540 			ignoredups = 1;
541 		else if (strcmp(tok, "ignorespace") == 0)
542 			ignorespace = 1;
543 	}
544 	afree(spec, ATEMP);
545 }
546 
547 /*
548  *	set history
549  *	this means reallocating the dataspace
550  */
551 void
sethistsize(int n)552 sethistsize(int n)
553 {
554 	if (n > 0 && (uint32_t)n != histsize) {
555 		char **tmp;
556 		int offset = histptr - history;
557 
558 		/* save most recent history */
559 		if (offset > n - 1) {
560 			char **hp;
561 
562 			offset = n - 1;
563 			for (hp = history; hp < histptr - offset; hp++)
564 				afree(*hp, APERM);
565 			memmove(history, histptr - offset, n * sizeof(char *));
566 		}
567 
568 		tmp = reallocarray(histbase, n + 1, sizeof(char *));
569 		if (tmp != NULL) {
570 			histbase = tmp;
571 			histsize = n;
572 			history = histbase + 1;
573 			histptr = history + offset;
574 		} else
575 			warningf(false, "resizing history storage: %s",
576 			    strerror(errno));
577 	}
578 }
579 
580 /*
581  *	set history file
582  *	This can mean reloading/resetting/starting history file
583  *	maintenance
584  */
585 void
sethistfile(const char * name)586 sethistfile(const char *name)
587 {
588 	/* if not started then nothing to do */
589 	if (hstarted == 0)
590 		return;
591 
592 	/* if the name is the same as the name we have */
593 	if (hname && strcmp(hname, name) == 0)
594 		return;
595 	/*
596 	 * its a new name - possibly
597 	 */
598 	if (hname) {
599 		afree(hname, APERM);
600 		hname = NULL;
601 		histreset();
602 	}
603 
604 	history_close();
605 	hist_init(hist_source);
606 }
607 
608 /*
609  *	initialise the history vector
610  */
611 void
init_histvec(void)612 init_histvec(void)
613 {
614 	if (histbase == NULL) {
615 		histsize = HISTORYSIZE;
616 		/*
617 		 * allocate one extra element so that histptr always
618 		 * lies within array bounds
619 		 */
620 		histbase = reallocarray(NULL, histsize + 1, sizeof(char *));
621 		if (histbase == NULL)
622 			internal_errorf("allocating history storage: %s",
623 			    strerror(errno));
624 		*histbase = NULL;
625 		history = histbase + 1;
626 		histptr = history - 1;
627 	}
628 }
629 
630 static void
history_lock(int operation)631 history_lock(int operation)
632 {
633 	while (flock(fileno(histfh), operation) != 0) {
634 		if (errno == EINTR || errno == EAGAIN)
635 			continue;
636 		else
637 			break;
638 	}
639 }
640 
641 /*
642  *	Routines added by Peter Collinson BSDI(Europe)/Hillside Systems to
643  *	a) permit HISTSIZE to control number of lines of history stored
644  *	b) maintain a physical history file
645  *
646  *	It turns out that there is a lot of ghastly hackery here
647  */
648 
649 
650 /*
651  * save command in history
652  */
653 void
histsave(int lno,const char * cmd,int dowrite)654 histsave(int lno, const char *cmd, int dowrite)
655 {
656 	char		*c, *cp;
657 
658 	if (ignorespace && cmd[0] == ' ')
659 		return;
660 
661 	c = str_save(cmd, APERM);
662 	if ((cp = strrchr(c, '\n')) != NULL)
663 		*cp = '\0';
664 
665 	/*
666 	 * XXX to properly check for duplicated lines we should first reload
667 	 * the histfile if needed
668 	 */
669 	if (ignoredups && histptr >= history && strcmp(*histptr, c) == 0) {
670 		afree(c, APERM);
671 		return;
672 	}
673 
674 	if (dowrite && histfh) {
675 #ifndef SMALL
676 		struct stat	sb;
677 
678 		history_lock(LOCK_EX);
679 		if (fstat(fileno(histfh), &sb) != -1) {
680 			if (timespeccmp(&sb.st_mtim, &last_sb.st_mtim, ==))
681 				; /* file is unchanged */
682 			else {
683 				histreset();
684 				history_load(hist_source);
685 			}
686 		}
687 #endif
688 	}
689 
690 	if (histptr < history + histsize - 1)
691 		histptr++;
692 	else { /* remove oldest command */
693 		afree(*history, APERM);
694 		memmove(history, history + 1,
695 		    (histsize - 1) * sizeof(*history));
696 	}
697 	*histptr = c;
698 
699 	if (dowrite && histfh) {
700 #ifndef SMALL
701 		char *encoded;
702 
703 		/* append to file */
704 		if (fseeko(histfh, 0, SEEK_END) == 0 &&
705 		    stravis(&encoded, c, VIS_SAFE | VIS_NL) != -1) {
706 			fprintf(histfh, "%s\n", encoded);
707 			fflush(histfh);
708 			fstat(fileno(histfh), &last_sb);
709 			line_co++;
710 			history_write();
711 			free(encoded);
712 		}
713 		history_lock(LOCK_UN);
714 #endif
715 	}
716 }
717 
718 static FILE *
history_open(void)719 history_open(void)
720 {
721 	FILE		*f = NULL;
722 #ifndef SMALL
723 	struct stat	sb;
724 	int		fd, fddup;
725 
726 	if ((fd = open(hname, O_RDWR | O_CREAT | O_EXLOCK, 0600)) == -1)
727 		return NULL;
728 	if (fstat(fd, &sb) == -1 || sb.st_uid != getuid()) {
729 		close(fd);
730 		return NULL;
731 	}
732 	fddup = savefd(fd);
733 	if (fddup != fd)
734 		close(fd);
735 
736 	if ((f = fdopen(fddup, "r+")) == NULL)
737 		close(fddup);
738 	else
739 		last_sb = sb;
740 #endif
741 	return f;
742 }
743 
744 static void
history_close(void)745 history_close(void)
746 {
747 	if (histfh) {
748 		fflush(histfh);
749 		fclose(histfh);
750 		histfh = NULL;
751 	}
752 }
753 
754 static void
history_load(Source * s)755 history_load(Source *s)
756 {
757 	char		*p, encoded[LINE + 1], line[LINE + 1];
758 	int		 toolongseen = 0;
759 
760 	rewind(histfh);
761 	line_co = 1;
762 
763 	/* just read it all; will auto resize history upon next command */
764 	while (fgets(encoded, sizeof(encoded), histfh)) {
765 		if ((p = strchr(encoded, '\n')) == NULL) {
766 			/* discard overlong line */
767 			do {
768 				/* maybe a missing trailing newline? */
769 				if (strlen(encoded) != sizeof(encoded) - 1) {
770 					bi_errorf("history file is corrupt");
771 					return;
772 				}
773 			} while (fgets(encoded, sizeof(encoded), histfh)
774 			    && strchr(encoded, '\n') == NULL);
775 
776 			if (!toolongseen) {
777 				toolongseen = 1;
778 				bi_errorf("ignored history line(s) longer than"
779 				    " %d bytes", LINE);
780 			}
781 
782 			continue;
783 		}
784 		*p = '\0';
785 		s->line = line_co;
786 		s->cmd_offset = line_co;
787 		strunvis(line, encoded);
788 		histsave(line_co, line, 0);
789 		line_co++;
790 	}
791 
792 	history_write();
793 }
794 
795 #define HMAGIC1 0xab
796 #define HMAGIC2 0xcd
797 
798 void
hist_init(Source * s)799 hist_init(Source *s)
800 {
801 	int oldmagic1, oldmagic2;
802 
803 	if (Flag(FTALKING) == 0)
804 		return;
805 
806 	hstarted = 1;
807 
808 	hist_source = s;
809 
810 	if (str_val(global("HISTFILE")) == null)
811 		return;
812 	hname = str_save(str_val(global("HISTFILE")), APERM);
813 	histfh = history_open();
814 	if (histfh == NULL)
815 		return;
816 
817 	oldmagic1 = fgetc(histfh);
818 	oldmagic2 = fgetc(histfh);
819 
820 	if (oldmagic1 == EOF || oldmagic2 == EOF) {
821 		if (!feof(histfh) && ferror(histfh)) {
822 			history_close();
823 			return;
824 		}
825 	} else if (oldmagic1 == HMAGIC1 && oldmagic2 == HMAGIC2) {
826 		bi_errorf("ignoring old style history file");
827 		history_close();
828 		return;
829 	}
830 
831 	history_load(s);
832 
833 	history_lock(LOCK_UN);
834 }
835 
836 static void
history_write(void)837 history_write(void)
838 {
839 	char		**hp, *encoded;
840 
841 	/* see if file has grown over 25% */
842 	if (line_co < histsize + (histsize / 4))
843 		return;
844 
845 	/* rewrite the whole caboodle */
846 	rewind(histfh);
847 	if (ftruncate(fileno(histfh), 0) == -1) {
848 		bi_errorf("failed to rewrite history file - %s",
849 		    strerror(errno));
850 	}
851 	for (hp = history; hp <= histptr; hp++) {
852 		if (stravis(&encoded, *hp, VIS_SAFE | VIS_NL) != -1) {
853 			if (fprintf(histfh, "%s\n", encoded) == -1) {
854 				free(encoded);
855 				return;
856 			}
857 			free(encoded);
858 		}
859 	}
860 
861 	line_co = histsize;
862 
863 	fflush(histfh);
864 	fstat(fileno(histfh), &last_sb);
865 }
866 
867 void
hist_finish(void)868 hist_finish(void)
869 {
870 	history_close();
871 }
872