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