Unleashed v1.4
[unleashed.git] / bin / ksh / history.c
blobdf5f8687060a83b7c7dc62919d7fa25727123d2b
1 /* $OpenBSD: history.c,v 1.80 2018/01/15 22:30:38 jca Exp $ */
3 /*
4 * command history
5 */
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.
15 #include <sys/stat.h>
16 #include <sys/uio.h>
18 #include <errno.h>
19 #include <fcntl.h>
20 #include <stdlib.h>
21 #include <stdio.h>
22 #include <string.h>
23 #include <unistd.h>
24 #include <vis.h>
26 #include "sh.h"
28 static void history_write(void);
29 static FILE *history_open(void);
30 static void history_load(Source *);
31 static void history_close(void);
33 static int hist_execute(char *);
34 static int hist_replace(char **, const char *, const char *, int);
35 static char **hist_get(const char *, int, int);
36 static char **hist_get_oldest(void);
37 static void histbackup(void);
39 static FILE *histfh;
40 static char **histbase; /* actual start of the history[] allocation */
41 static char **current; /* current position in history[] */
42 static char *hname; /* current name of history file */
43 static int hstarted; /* set after hist_init() called */
44 static int ignoredups; /* ditch duplicated history lines? */
45 static int ignorespace; /* ditch lines starting with a space? */
46 static Source *hist_source;
47 static uint32_t line_co;
49 static struct stat last_sb;
51 int
52 c_fc(char **wp)
54 struct shf *shf;
55 struct temp *tf = NULL;
56 char *p, *editor = NULL;
57 int gflag = 0, lflag = 0, nflag = 0, sflag = 0, rflag = 0;
58 int optc, ret;
59 char *first = NULL, *last = NULL;
60 char **hfirst, **hlast, **hp;
61 static int depth;
63 if (depth != 0) {
64 bi_errorf("history function called recursively");
65 return 1;
68 if (!Flag(FTALKING_I)) {
69 bi_errorf("history functions not available");
70 return 1;
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);
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;
114 break;
115 case '?':
116 return 1;
118 wp += builtin_opt.optind;
120 /* Substitute and execute command */
121 if (sflag) {
122 char *pat = NULL, *rep = NULL;
124 if (editor || lflag || nflag || rflag) {
125 bi_errorf("can't use -e, -l, -n, -r with -s (-e -)");
126 return 1;
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++;
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;
145 hp = first ? hist_get(first, false, false) :
146 hist_get_newest(false);
147 if (!hp)
148 return 1;
149 depth++;
150 ret = hist_replace(hp, pat, rep, gflag);
151 depth--;
152 return ret;
155 if (editor && (lflag || nflag)) {
156 bi_errorf("can't use -l, -n with -e");
157 return 1;
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;
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.
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;
189 if (hfirst > hlast) {
190 char **temp;
192 temp = hfirst; hfirst = hlast; hlast = temp;
193 rflag = !rflag; /* POSIX */
196 /* List history */
197 if (lflag) {
198 char *s, *t;
199 const char *nfmt = nflag ? "\t" : "%d\t";
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);
210 shf_flush(shl_stdout);
211 return 0;
214 /* Run editor on selected lines, then run resulting commands */
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;
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;
230 /* Ignore setstr errors here (arbitrary) */
231 setstr(local("_", false), tf->name, KSH_RETURN_ERROR);
233 /* XXX: source should not get trashed by this.. */
235 Source *sold = source;
237 ret = command(editor ? editor : "${FCEDIT:-/bin/ed} $_", 0);
238 source = sold;
239 if (ret)
240 return ret;
244 struct stat statb;
245 XString xs;
246 char *xp;
247 int n;
249 if (!(shf = shf_open(tf->name, O_RDONLY, 0, 0))) {
250 bi_errorf("cannot open temp file %s", tf->name);
251 return 1;
254 n = fstat(shf->fd, &statb) < 0 ? 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));
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;
268 shf_close(shf);
269 *xp = '\0';
270 strip_nuls(Xstring(xs, xp), Xlength(xs, xp));
271 depth++;
272 ret = hist_execute(Xstring(xs, xp));
273 depth--;
274 return ret;
278 /* Save cmd in history, execute cmd (cmd gets trashed) */
279 static int
280 hist_execute(char *cmd)
282 Source *sold;
283 int ret;
284 char *p, *q;
286 histbackup();
288 for (p = cmd; p; p = q) {
289 if ((q = strchr(p, '\n'))) {
290 *q++ = '\0'; /* kill the newline */
291 if (!*q) /* ignore trailing newline */
292 q = NULL;
294 histsave(++(hist_source->line), p, 1);
296 shellf("%s\n", p); /* POSIX doesn't say this is done... */
297 if ((p = q)) /* restore \n (trailing \n not restored) */
298 q[-1] = '\n';
301 /* Commands are executed here instead of pushing them onto the
302 * input 'cause posix says the redirection and variable assignments
303 * in
304 * X=y fc -e - 42 2> /dev/null
305 * are to effect the repeated commands environment.
307 /* XXX: source should not get trashed by this.. */
308 sold = source;
309 ret = command(cmd, 0);
310 source = sold;
311 return ret;
314 static int
315 hist_replace(char **hp, const char *pat, const char *rep, int global)
317 char *line;
319 if (!pat)
320 line = str_save(*hp, ATEMP);
321 else {
322 char *s, *s1;
323 int pat_len = strlen(pat);
324 int rep_len = strlen(rep);
325 int len;
326 XString xs;
327 char *xp;
328 int any_subst = 0;
330 Xinit(xs, xp, 128, ATEMP);
331 for (s = *hp; (s1 = strstr(s, pat)) && (!any_subst || global);
332 s = s1 + pat_len) {
333 any_subst = 1;
334 len = s1 - s;
335 XcheckN(xs, xp, len + rep_len);
336 memcpy(xp, s, len); /* first part */
337 xp += len;
338 memcpy(xp, rep, rep_len); /* replacement */
339 xp += rep_len;
341 if (!any_subst) {
342 bi_errorf("substitution failed");
343 return 1;
345 len = strlen(s) + 1;
346 XcheckN(xs, xp, len);
347 memcpy(xp, s, len);
348 xp += len;
349 line = Xclose(xs, xp);
351 return hist_execute(line);
355 * get pointer to history given pattern
356 * pattern is a number or string
358 static char **
359 hist_get(const char *str, int approx, int allow_cur)
361 char **hp = NULL;
362 int n;
364 if (getn(str, &n)) {
365 hp = histptr + (n < 0 ? n : (n - hist_source->line));
366 if ((long)hp < (long)history) {
367 if (approx)
368 hp = hist_get_oldest();
369 else {
370 bi_errorf("%s: not in history", str);
371 hp = NULL;
373 } else if (hp > histptr) {
374 if (approx)
375 hp = hist_get_newest(allow_cur);
376 else {
377 bi_errorf("%s: not in history", str);
378 hp = NULL;
380 } else if (!allow_cur && hp == histptr) {
381 bi_errorf("%s: invalid range", str);
382 hp = NULL;
384 } else {
385 int anchored = *str == '?' ? (++str, 0) : 1;
387 /* the -1 is to avoid the current fc command */
388 n = findhist(histptr - history - 1, 0, str, anchored);
389 if (n < 0) {
390 bi_errorf("%s: not in history", str);
391 hp = NULL;
392 } else
393 hp = &history[n];
395 return hp;
398 /* Return a pointer to the newest command in the history */
399 char **
400 hist_get_newest(int allow_cur)
402 if (histptr < history || (!allow_cur && histptr == history)) {
403 bi_errorf("no history (yet)");
404 return NULL;
406 if (allow_cur)
407 return histptr;
408 return histptr - 1;
411 /* Return a pointer to the oldest command in the history */
412 static char **
413 hist_get_oldest(void)
415 if (histptr <= history) {
416 bi_errorf("no history (yet)");
417 return NULL;
419 return history;
422 /******************************/
423 /* Back up over last histsave */
424 /******************************/
425 static void
426 histbackup(void)
428 static int last_line = -1;
430 if (histptr >= history && last_line != hist_source->line) {
431 hist_source->line--;
432 afree(*histptr, APERM);
433 histptr--;
434 last_line = hist_source->line;
438 static void
439 histreset(void)
441 char **hp;
443 for (hp = history; hp <= histptr; hp++)
444 afree(*hp, APERM);
446 histptr = history - 1;
447 hist_source->line = 0;
451 * Return the current position.
453 char **
454 histpos(void)
456 return current;
460 histnum(int n)
462 int last = histptr - history;
464 if (n < 0 || n >= last) {
465 current = histptr;
466 return last;
467 } else {
468 current = &history[n];
469 return n;
474 * This will become unnecessary if hist_get is modified to allow
475 * searching from positions other than the end, and in either
476 * direction.
479 findhist(int start, int fwd, const char *str, int anchored)
481 char **hp;
482 int maxhist = histptr - history;
483 int incr = fwd ? 1 : -1;
484 int len = strlen(str);
486 if (start < 0 || start >= maxhist)
487 start = maxhist;
489 hp = &history[start];
490 for (; hp >= history && hp <= histptr; hp += incr)
491 if ((anchored && strncmp(*hp, str, len) == 0) ||
492 (!anchored && strstr(*hp, str)))
493 return hp - history;
495 return -1;
499 findhistrel(const char *str)
501 int maxhist = histptr - history;
502 int start = maxhist - 1;
503 int rec = atoi(str);
505 if (rec == 0)
506 return -1;
507 if (rec > 0) {
508 if (rec > maxhist)
509 return -1;
510 return rec - 1;
512 if (rec > maxhist)
513 return -1;
514 return start + rec + 1;
517 void
518 sethistcontrol(const char *str)
520 char *spec, *tok, *state;
522 ignorespace = 0;
523 ignoredups = 0;
525 if (str == NULL)
526 return;
528 spec = str_save(str, ATEMP);
529 for (tok = strtok_r(spec, ":", &state); tok != NULL;
530 tok = strtok_r(NULL, ":", &state)) {
531 if (strcmp(tok, "ignoredups") == 0)
532 ignoredups = 1;
533 else if (strcmp(tok, "ignorespace") == 0)
534 ignorespace = 1;
536 afree(spec, ATEMP);
540 * set history
541 * this means reallocating the dataspace
543 void
544 sethistsize(int n)
546 if (n > 0 && (uint32_t)n != histsize) {
547 int offset = histptr - history;
549 /* save most recent history */
550 if (offset > n - 1) {
551 char **hp;
553 offset = n - 1;
554 for (hp = history; hp < histptr - offset; hp++)
555 afree(*hp, APERM);
556 memmove(history, histptr - offset, n * sizeof(char *));
559 histsize = n;
560 histbase = areallocarray(histbase, n + 1, sizeof(char *), APERM);
561 history = histbase + 1;
562 histptr = history + offset;
567 * set history file
568 * This can mean reloading/resetting/starting history file
569 * maintenance
571 void
572 sethistfile(const char *name)
574 /* if not started then nothing to do */
575 if (hstarted == 0)
576 return;
578 /* if the name is the same as the name we have */
579 if (hname && strcmp(hname, name) == 0)
580 return;
582 * its a new name - possibly
584 if (hname) {
585 afree(hname, APERM);
586 hname = NULL;
587 histreset();
590 history_close();
591 hist_init(hist_source);
595 * initialise the history vector
597 void
598 init_histvec(void)
600 if (histbase == NULL) {
601 histsize = HISTORYSIZE;
603 * allocate one extra element so that histptr always
604 * lies within array bounds
606 histbase = areallocarray(NULL, histsize + 1, sizeof(char *),
607 APERM);
608 *histbase = NULL;
609 history = histbase + 1;
610 histptr = history - 1;
614 static void
615 history_lock(int operation)
617 while (flock(fileno(histfh), operation) != 0) {
618 if (errno == EINTR || errno == EAGAIN)
619 continue;
620 else
621 break;
626 * Routines added by Peter Collinson BSDI(Europe)/Hillside Systems to
627 * a) permit HISTSIZE to control number of lines of history stored
628 * b) maintain a physical history file
630 * It turns out that there is a lot of ghastly hackery here
635 * save command in history
637 void
638 histsave(int lno, const char *cmd, int dowrite)
640 char *c, *cp;
642 if (ignorespace && cmd[0] == ' ')
643 return;
645 c = str_save(cmd, APERM);
646 if ((cp = strrchr(c, '\n')) != NULL)
647 *cp = '\0';
650 * XXX to properly check for duplicated lines we should first reload
651 * the histfile if needed
653 if (ignoredups && histptr >= history && strcmp(*histptr, c) == 0) {
654 afree(c, APERM);
655 return;
658 if (dowrite && histfh) {
659 #ifndef SMALL
660 struct stat sb;
662 history_lock(LOCK_EX);
663 if (fstat(fileno(histfh), &sb) != -1) {
664 if (timespeccmp(&sb.st_mtim, &last_sb.st_mtim, ==))
665 ; /* file is unchanged */
666 else {
667 histreset();
668 history_load(hist_source);
671 #endif
674 if (histptr < history + histsize - 1)
675 histptr++;
676 else { /* remove oldest command */
677 afree(*history, APERM);
678 memmove(history, history + 1,
679 (histsize - 1) * sizeof(*history));
681 *histptr = c;
683 if (dowrite && histfh) {
684 #ifndef SMALL
685 char *encoded;
687 /* append to file */
688 if (fseeko(histfh, 0, SEEK_END) == 0 &&
689 stravis(&encoded, c, VIS_SAFE | VIS_NL) != -1) {
690 fprintf(histfh, "%s\n", encoded);
691 fflush(histfh);
692 fstat(fileno(histfh), &last_sb);
693 line_co++;
694 history_write();
695 free(encoded);
697 history_lock(LOCK_UN);
698 #endif
702 static FILE *
703 history_open(void)
705 FILE *f = NULL;
706 #ifndef SMALL
707 struct stat sb;
708 int fd, fddup;
710 if ((fd = open(hname, O_RDWR | O_CREAT, 0600)) == -1)
711 return NULL;
712 if (flock(fd, LOCK_EX) == -1 ||
713 fstat(fd, &sb) == -1 || sb.st_uid != getuid()) {
714 close(fd);
715 return NULL;
717 fddup = savefd(fd);
718 if (fddup != fd)
719 close(fd);
721 if ((f = fdopen(fddup, "r+")) == NULL)
722 close(fddup);
723 else
724 last_sb = sb;
725 #endif
726 return f;
729 static void
730 history_close(void)
732 if (histfh) {
733 fflush(histfh);
734 fclose(histfh);
735 histfh = NULL;
739 static void
740 history_load(Source *s)
742 char *p, encoded[LINE + 1], line[LINE + 1];
743 int toolongseen = 0;
745 rewind(histfh);
746 line_co = 1;
748 /* just read it all; will auto resize history upon next command */
749 while (fgets(encoded, sizeof(encoded), histfh)) {
750 if ((p = strchr(encoded, '\n')) == NULL) {
751 /* discard overlong line */
752 do {
753 /* maybe a missing trailing newline? */
754 if (strlen(encoded) != sizeof(encoded) - 1) {
755 bi_errorf("history file is corrupt");
756 return;
758 } while (fgets(encoded, sizeof(encoded), histfh)
759 && strchr(encoded, '\n') == NULL);
761 if (!toolongseen) {
762 toolongseen = 1;
763 bi_errorf("ignored history line(s) longer than"
764 " %d bytes", LINE);
767 continue;
769 *p = '\0';
770 s->line = line_co;
771 s->cmd_offset = line_co;
772 strunvis(line, encoded);
773 histsave(line_co, line, 0);
774 line_co++;
777 history_write();
780 #define HMAGIC1 0xab
781 #define HMAGIC2 0xcd
783 void
784 hist_init(Source *s)
786 int oldmagic1, oldmagic2;
788 if (Flag(FTALKING) == 0)
789 return;
791 hstarted = 1;
793 hist_source = s;
795 if (str_val(global("HISTFILE")) == null)
796 return;
797 hname = str_save(str_val(global("HISTFILE")), APERM);
798 histfh = history_open();
799 if (histfh == NULL)
800 return;
802 oldmagic1 = fgetc(histfh);
803 oldmagic2 = fgetc(histfh);
805 if (oldmagic1 == EOF || oldmagic2 == EOF) {
806 if (!feof(histfh) && ferror(histfh)) {
807 history_close();
808 return;
810 } else if (oldmagic1 == HMAGIC1 && oldmagic2 == HMAGIC2) {
811 bi_errorf("ignoring old style history file");
812 history_close();
813 return;
816 history_load(s);
818 history_lock(LOCK_UN);
821 static void
822 history_write(void)
824 char **hp, *encoded;
826 /* see if file has grown over 25% */
827 if (line_co < histsize + (histsize / 4))
828 return;
830 /* rewrite the whole caboodle */
831 rewind(histfh);
832 if (ftruncate(fileno(histfh), 0) == -1) {
833 bi_errorf("failed to rewrite history file - %s",
834 strerror(errno));
836 for (hp = history; hp <= histptr; hp++) {
837 if (stravis(&encoded, *hp, VIS_SAFE | VIS_NL) != -1) {
838 if (fprintf(histfh, "%s\n", encoded) == -1) {
839 free(encoded);
840 return;
842 free(encoded);
846 line_co = histsize;
848 fflush(histfh);
849 fstat(fileno(histfh), &last_sb);
852 void
853 hist_finish(void)
855 history_close();