loksh-noxz

[fork] a Linux port of OpenBSD's ksh
git clone https://noxz.tech/git/loksh-noxz.git
Log | Files | README

history.c
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
27static void	history_write(void);
28static FILE	*history_open(void);
29static void	history_load(Source *);
30static void	history_close(void);
31
32static int	hist_execute(char *);
33static int	hist_replace(char **, const char *, const char *, int);
34static char   **hist_get(const char *, int, int);
35static char   **hist_get_oldest(void);
36static void	histbackup(void);
37
38static FILE	*histfh;
39static char   **histbase;	/* actual start of the history[] allocation */
40static char   **current;	/* current position in history[] */
41static char    *hname;		/* current name of history file */
42static int	hstarted;	/* set after hist_init() called */
43static int	ignoredups;	/* ditch duplicated history lines? */
44static int	ignorespace;	/* ditch lines starting with a space? */
45static Source	*hist_source;
46static uint32_t	line_co;
47
48static struct stat last_sb;
49
50static volatile sig_atomic_t	c_fc_depth;
51
52int
53c_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 */
281void
282c_fc_reset(void)
283{
284	c_fc_depth = 0;
285}
286
287/* Save cmd in history, execute cmd (cmd gets trashed) */
288static int
289hist_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
323static int
324hist_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 */
367static char **
368hist_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 */
408char **
409hist_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 */
421static char **
422hist_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/******************************/
434static void
435histbackup(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
447static void
448histreset(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 */
462char **
463histpos(void)
464{
465	return current;
466}
467
468int
469histnum(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 */
487int
488findhist(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
507int
508findhistrel(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
526void
527sethistcontrol(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 */
552void
553sethistsize(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 */
586void
587sethistfile(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 */
612void
613init_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
631static void
632history_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 */
654void
655histsave(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
719static FILE *
720history_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
745static void
746history_close(void)
747{
748	if (histfh) {
749		fflush(histfh);
750		fclose(histfh);
751		histfh = NULL;
752	}
753}
754
755static void
756history_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
799void
800hist_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
837static void
838history_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
868void
869hist_finish(void)
870{
871	history_close();
872}