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}