oksh-noxz

[fork] Portable OpenBSD ksh, based on the Public Domain Korn Shell (pdksh).
git clone https://noxz.tech/git/oksh-noxz.git
Log | Files | Tags

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