sync
[bitrig.git] / bin / ksh / history.c
blob8060cef472279d17642c96d552124174ecea0360
1 /* $OpenBSD: history.c,v 1.39 2010/05/19 17:36:08 jasper Exp $ */
3 #include "sh.h"
5 #ifdef HISTORY
6 #include <sys/stat.h>
7 #include <sys/file.h>
8 #include <sys/param.h>
9 #include <sys/mount.h>
11 static void writehistfile(void);
12 static FILE *history_open(void);
13 static int history_load(Source *);
14 static void history_close(void);
16 static int hist_execute(char *);
17 static int hist_replace(char **, const char *, const char *, int);
18 static char **hist_get(const char *, int, int);
19 static char **hist_get_oldest(void);
20 static void histbackup(void);
22 static FILE *histfd;
23 static char **current; /* current position in history[] */
24 static char *hname; /* current name of history file */
25 static int hstarted; /* set after hist_init() called */
26 static Source *hist_source;
27 static uint32_t line_co;
28 static int real_fs = 0;
29 static uint32_t ro_fs = 0;
30 static int lockfd = -1;
31 static char *lname;
33 static struct stat last_sb;
35 int
36 c_fc(char **wp)
38 struct shf *shf;
39 struct temp *tf = NULL;
40 char *p, *editor = NULL;
41 int gflag = 0, lflag = 0, nflag = 0, sflag = 0, rflag = 0;
42 int optc;
43 char *first = NULL, *last = NULL;
44 char **hfirst, **hlast, **hp;
46 if (!Flag(FTALKING_I)) {
47 bi_errorf("history functions not available");
48 return 1;
51 while ((optc = ksh_getopt(wp, &builtin_opt,
52 "e:glnrs0,1,2,3,4,5,6,7,8,9,")) != -1)
53 switch (optc) {
54 case 'e':
55 p = builtin_opt.optarg;
56 if (strcmp(p, "-") == 0)
57 sflag++;
58 else {
59 size_t len = strlen(p) + 4;
60 editor = str_nsave(p, len, ATEMP);
61 strlcat(editor, " $_", len);
63 break;
64 case 'g': /* non-at&t ksh */
65 gflag++;
66 break;
67 case 'l':
68 lflag++;
69 break;
70 case 'n':
71 nflag++;
72 break;
73 case 'r':
74 rflag++;
75 break;
76 case 's': /* posix version of -e - */
77 sflag++;
78 break;
79 /* kludge city - accept -num as -- -num (kind of) */
80 case '0': case '1': case '2': case '3': case '4':
81 case '5': case '6': case '7': case '8': case '9':
82 p = shf_smprintf("-%c%s",
83 optc, builtin_opt.optarg);
84 if (!first)
85 first = p;
86 else if (!last)
87 last = p;
88 else {
89 bi_errorf("too many arguments");
90 return 1;
92 break;
93 case '?':
94 return 1;
96 wp += builtin_opt.optind;
98 /* Substitute and execute command */
99 if (sflag) {
100 char *pat = NULL, *rep = NULL;
102 if (editor || lflag || nflag || rflag) {
103 bi_errorf("can't use -e, -l, -n, -r with -s (-e -)");
104 return 1;
107 /* Check for pattern replacement argument */
108 if (*wp && **wp && (p = strchr(*wp + 1, '='))) {
109 pat = str_save(*wp, ATEMP);
110 p = pat + (p - *wp);
111 *p++ = '\0';
112 rep = p;
113 wp++;
115 /* Check for search prefix */
116 if (!first && (first = *wp))
117 wp++;
118 if (last || *wp) {
119 bi_errorf("too many arguments");
120 return 1;
123 hp = first ? hist_get(first, false, false) :
124 hist_get_newest(false);
125 if (!hp)
126 return 1;
127 return hist_replace(hp, pat, rep, gflag);
130 if (editor && (lflag || nflag)) {
131 bi_errorf("can't use -l, -n with -e");
132 return 1;
135 if (!first && (first = *wp))
136 wp++;
137 if (!last && (last = *wp))
138 wp++;
139 if (*wp) {
140 bi_errorf("too many arguments");
141 return 1;
143 if (!first) {
144 hfirst = lflag ? hist_get("-16", true, true) :
145 hist_get_newest(false);
146 if (!hfirst)
147 return 1;
148 /* can't fail if hfirst didn't fail */
149 hlast = hist_get_newest(false);
150 } else {
151 /* POSIX says not an error if first/last out of bounds
152 * when range is specified; at&t ksh and pdksh allow out of
153 * bounds for -l as well.
155 hfirst = hist_get(first, (lflag || last) ? true : false,
156 lflag ? true : false);
157 if (!hfirst)
158 return 1;
159 hlast = last ? hist_get(last, true, lflag ? true : false) :
160 (lflag ? hist_get_newest(false) : hfirst);
161 if (!hlast)
162 return 1;
164 if (hfirst > hlast) {
165 char **temp;
167 temp = hfirst; hfirst = hlast; hlast = temp;
168 rflag = !rflag; /* POSIX */
171 /* List history */
172 if (lflag) {
173 char *s, *t;
174 const char *nfmt = nflag ? "\t" : "%d\t";
176 for (hp = rflag ? hlast : hfirst;
177 hp >= hfirst && hp <= hlast; hp += rflag ? -1 : 1) {
178 shf_fprintf(shl_stdout, nfmt,
179 hist_source->line - (int) (histptr - hp));
180 /* print multi-line commands correctly */
181 for (s = *hp; (t = strchr(s, '\n')); s = t)
182 shf_fprintf(shl_stdout, "%.*s\t", ++t - s, s);
183 shf_fprintf(shl_stdout, "%s\n", s);
185 shf_flush(shl_stdout);
186 return 0;
189 /* Run editor on selected lines, then run resulting commands */
191 tf = maketemp(ATEMP, TT_HIST_EDIT, &e->temps);
192 if (!(shf = tf->shf)) {
193 bi_errorf("cannot create temp file %s - %s",
194 tf->name, strerror(errno));
195 return 1;
197 for (hp = rflag ? hlast : hfirst;
198 hp >= hfirst && hp <= hlast; hp += rflag ? -1 : 1)
199 shf_fprintf(shf, "%s\n", *hp);
200 if (shf_close(shf) == EOF) {
201 bi_errorf("error writing temporary file - %s", strerror(errno));
202 return 1;
205 /* Ignore setstr errors here (arbitrary) */
206 setstr(local("_", false), tf->name, KSH_RETURN_ERROR);
208 /* XXX: source should not get trashed by this.. */
210 Source *sold = source;
211 int ret;
213 ret = command(editor ? editor : "${FCEDIT:-/bin/ed} $_", 0);
214 source = sold;
215 if (ret)
216 return ret;
220 struct stat statb;
221 XString xs;
222 char *xp;
223 int n;
225 if (!(shf = shf_open(tf->name, O_RDONLY, 0, 0))) {
226 bi_errorf("cannot open temp file %s", tf->name);
227 return 1;
230 n = fstat(shf_fileno(shf), &statb) < 0 ? 128 :
231 statb.st_size + 1;
232 Xinit(xs, xp, n, hist_source->areap);
233 while ((n = shf_read(xp, Xnleft(xs, xp), shf)) > 0) {
234 xp += n;
235 if (Xnleft(xs, xp) <= 0)
236 XcheckN(xs, xp, Xlength(xs, xp));
238 if (n < 0) {
239 bi_errorf("error reading temp file %s - %s",
240 tf->name, strerror(shf_errno(shf)));
241 shf_close(shf);
242 return 1;
244 shf_close(shf);
245 *xp = '\0';
246 strip_nuls(Xstring(xs, xp), Xlength(xs, xp));
247 return hist_execute(Xstring(xs, xp));
251 /* Save cmd in history, execute cmd (cmd gets trashed) */
252 static int
253 hist_execute(char *cmd)
255 Source *sold;
256 int ret;
257 char *p, *q;
259 histbackup();
261 for (p = cmd; p; p = q) {
262 if ((q = strchr(p, '\n'))) {
263 *q++ = '\0'; /* kill the newline */
264 if (!*q) /* ignore trailing newline */
265 q = NULL;
267 histsave(++(hist_source->line), p, 1);
269 shellf("%s\n", p); /* POSIX doesn't say this is done... */
270 if ((p = q)) /* restore \n (trailing \n not restored) */
271 q[-1] = '\n';
274 /* Commands are executed here instead of pushing them onto the
275 * input 'cause posix says the redirection and variable assignments
276 * in
277 * X=y fc -e - 42 2> /dev/null
278 * are to effect the repeated commands environment.
280 /* XXX: source should not get trashed by this.. */
281 sold = source;
282 ret = command(cmd, 0);
283 source = sold;
284 return ret;
287 static int
288 hist_replace(char **hp, const char *pat, const char *rep, int global)
290 char *line;
292 if (!pat)
293 line = str_save(*hp, ATEMP);
294 else {
295 char *s, *s1;
296 int pat_len = strlen(pat);
297 int rep_len = strlen(rep);
298 int len;
299 XString xs;
300 char *xp;
301 int any_subst = 0;
303 Xinit(xs, xp, 128, ATEMP);
304 for (s = *hp; (s1 = strstr(s, pat)) && (!any_subst || global);
305 s = s1 + pat_len) {
306 any_subst = 1;
307 len = s1 - s;
308 XcheckN(xs, xp, len + rep_len);
309 memcpy(xp, s, len); /* first part */
310 xp += len;
311 memcpy(xp, rep, rep_len); /* replacement */
312 xp += rep_len;
314 if (!any_subst) {
315 bi_errorf("substitution failed");
316 return 1;
318 len = strlen(s) + 1;
319 XcheckN(xs, xp, len);
320 memcpy(xp, s, len);
321 xp += len;
322 line = Xclose(xs, xp);
324 return hist_execute(line);
328 * get pointer to history given pattern
329 * pattern is a number or string
331 static char **
332 hist_get(const char *str, int approx, int allow_cur)
334 char **hp = NULL;
335 int n;
337 if (getn(str, &n)) {
338 hp = histptr + (n < 0 ? n : (n - hist_source->line));
339 if ((long)hp < (long)history) {
340 if (approx)
341 hp = hist_get_oldest();
342 else {
343 bi_errorf("%s: not in history", str);
344 hp = NULL;
346 } else if (hp > histptr) {
347 if (approx)
348 hp = hist_get_newest(allow_cur);
349 else {
350 bi_errorf("%s: not in history", str);
351 hp = NULL;
353 } else if (!allow_cur && hp == histptr) {
354 bi_errorf("%s: invalid range", str);
355 hp = NULL;
357 } else {
358 int anchored = *str == '?' ? (++str, 0) : 1;
360 /* the -1 is to avoid the current fc command */
361 n = findhist(histptr - history - 1, 0, str, anchored);
362 if (n < 0) {
363 bi_errorf("%s: not in history", str);
364 hp = NULL;
365 } else
366 hp = &history[n];
368 return hp;
371 /* Return a pointer to the newest command in the history */
372 char **
373 hist_get_newest(int allow_cur)
375 if (histptr < history || (!allow_cur && histptr == history)) {
376 bi_errorf("no history (yet)");
377 return NULL;
379 if (allow_cur)
380 return histptr;
381 return histptr - 1;
384 /* Return a pointer to the oldest command in the history */
385 static char **
386 hist_get_oldest(void)
388 if (histptr <= history) {
389 bi_errorf("no history (yet)");
390 return NULL;
392 return history;
395 /******************************/
396 /* Back up over last histsave */
397 /******************************/
398 static void
399 histbackup(void)
401 static int last_line = -1;
403 if (histptr >= history && last_line != hist_source->line) {
404 hist_source->line--;
405 afree((void*)*histptr, APERM);
406 histptr--;
407 last_line = hist_source->line;
412 * Return the current position.
414 char **
415 histpos(void)
417 return current;
421 histnum(int n)
423 int last = histptr - history;
425 if (n < 0 || n >= last) {
426 current = histptr;
427 return last;
428 } else {
429 current = &history[n];
430 return n;
435 * This will become unnecessary if hist_get is modified to allow
436 * searching from positions other than the end, and in either
437 * direction.
440 findhist(int start, int fwd, const char *str, int anchored)
442 char **hp;
443 int maxhist = histptr - history;
444 int incr = fwd ? 1 : -1;
445 int len = strlen(str);
447 if (start < 0 || start >= maxhist)
448 start = maxhist;
450 hp = &history[start];
451 for (; hp >= history && hp <= histptr; hp += incr)
452 if ((anchored && strncmp(*hp, str, len) == 0) ||
453 (!anchored && strstr(*hp, str)))
454 return hp - history;
456 return -1;
460 findhistrel(const char *str)
462 int maxhist = histptr - history;
463 int start = maxhist - 1;
464 int rec = atoi(str);
466 if (rec == 0)
467 return -1;
468 if (rec > 0) {
469 if (rec > maxhist)
470 return -1;
471 return rec - 1;
473 if (rec > maxhist)
474 return -1;
475 return start + rec + 1;
479 * set history
480 * this means reallocating the dataspace
482 void
483 sethistsize(int n)
485 if (n > 0 && n != histsize) {
486 int cursize = histptr - history;
488 /* save most recent history */
489 if (n < cursize) {
490 memmove(history, histptr - n, n * sizeof(char *));
491 cursize = n;
494 history = (char **)aresize(history, n*sizeof(char *), APERM);
496 histsize = n;
497 histptr = history + cursize;
502 * set history file
503 * This can mean reloading/resetting/starting history file
504 * maintenance
506 void
507 sethistfile(const char *name)
509 /* if not started then nothing to do */
510 if (hstarted == 0)
511 return;
513 /* if the name is the same as the name we have */
514 if (hname && strcmp(hname, name) == 0)
515 return;
517 * its a new name - possibly
519 if (hname) {
520 afree(hname, APERM);
521 hname = NULL;
522 /* let's reset the history */
523 histptr = history - 1;
524 hist_source->line = 0;
527 if (histfd)
528 history_close();
530 hist_init(hist_source);
534 * initialise the history vector
536 void
537 init_histvec(void)
539 if (history == (char **)NULL) {
540 histsize = HISTORYSIZE;
541 history = (char **)alloc(histsize*sizeof (char *), APERM);
542 histptr = history - 1;
546 static int
547 history_lock(void)
549 int tries;
551 if (real_fs) {
552 while (flock(fileno(histfd), LOCK_EX) != 0) {
553 if (errno == EINTR || errno == EAGAIN)
554 continue;
555 else
556 break;
558 } else {
559 if (lockfd != -1) {
560 /* should not happen */
561 bi_errorf("recursive lock");
562 close(lockfd);
563 lockfd = -1;
565 for (tries = 0; ro_fs == 0 && tries < 30; tries++) {
566 if ((lockfd = open(lname, O_WRONLY | O_CREAT |
567 O_EXLOCK | O_EXCL, 0600)) != -1)
568 return (0);
569 usleep(100000); /* 100mS */
571 /* locking failed */
572 return (1);
575 return (0);
578 static void
579 history_unlock(void)
581 if (real_fs) {
582 while (flock(fileno(histfd), LOCK_UN) != 0) {
583 if (errno == EINTR || errno == EAGAIN)
584 continue;
585 else
586 break;
588 } else {
589 if (lockfd == -1) {
590 /* should not happen */
591 bi_errorf("invalid lock");
592 return;
594 if (unlink(lname) == -1) {
595 /* should not happen */
596 bi_errorf("can't unlink lock file");
597 return;
599 close(lockfd);
600 lockfd = -1;
605 * save command in history
607 void
608 histsave(int lno, const char *cmd, int dowrite)
610 char **hp;
611 char *c, *cp;
612 struct stat sb;
613 int gotlock = 0;
615 if (dowrite && histfd) {
616 if (history_lock() == 0) {
617 gotlock = 1;
618 if (fstat(fileno(histfd), &sb) != -1) {
619 if (timespeccmp(&sb.st_mtim,
620 &last_sb.st_mtim, ==))
621 ; /* file is unchanged */
622 else {
623 /* reset history */
624 histptr = history - 1;
625 hist_source->line = 0;
626 history_load(hist_source);
632 c = str_save(cmd, APERM);
633 if ((cp = strchr(c, '\n')) != NULL)
634 *cp = '\0';
636 hp = histptr;
637 if (++hp >= history + histsize) { /* remove oldest command */
638 afree((void*)*history, APERM);
639 for (hp = history; hp < history + histsize - 1; hp++)
640 hp[0] = hp[1];
642 *hp = c;
643 histptr = hp;
645 if (dowrite && histfd) {
646 if (gotlock) {
647 /* append to file */
648 if (fseeko(histfd, 0, SEEK_END) == 0) {
649 fprintf(histfd, "%s", cmd);
650 fflush(histfd);
651 fstat(fileno(histfd), &last_sb);
652 line_co++;
653 writehistfile();
655 history_unlock();
660 static FILE *
661 history_open(void)
663 int fd, fddup, flags;
664 FILE *f = NULL;
665 uint8_t old[2];
667 if (real_fs == 0) {
668 if (history_lock()) {
669 bi_errorf("initial locking failed, history file won't "
670 "be used");
671 return (NULL);
673 flags = O_RDWR | O_CREAT;
674 } else
675 flags = O_RDWR | O_CREAT | O_EXLOCK;
677 if ((fd = open(hname, flags, 0600)) == -1)
678 return (NULL);
680 fddup = savefd(fd);
681 if (fddup != fd)
682 close(fd);
684 f = fdopen(fddup, "r+");
685 if (f == NULL) {
686 close(fddup);
687 goto bad;
690 /* check for old format */
691 if (fread(old, sizeof old, 1, f) == 1) {
692 if (old[0] == 0xab && old[1] == 0xcd) {
693 bi_errorf("binary format history file detected, "
694 "history file won't be used");
695 goto bad;
697 rewind(f);
700 fstat(fileno(f), &last_sb);
702 return (f);
703 bad:
704 if (f)
705 fclose(f);
707 return (NULL);
710 static void
711 history_close(void)
713 if (histfd) {
714 fflush(histfd);
715 fclose(histfd);
716 histfd = NULL;
720 static int
721 history_load(Source *s)
723 char *p, line[LINE + 1];
725 rewind(histfd);
727 /* just read it all; will auto resize history upon next command */
728 for (line_co = 1; ; line_co++) {
729 p = fgets(line, sizeof line, histfd);
730 if (p == NULL || feof(histfd) || ferror(histfd))
731 break;
732 if ((p = strchr(line, '\n')) == NULL) {
733 bi_errorf("history file is corrupt");
734 return (1);
736 *p = '\0';
738 s->line = line_co;
739 s->cmd_offset = line_co;
740 histsave(line_co, (char *)line, 0);
743 writehistfile();
745 return (0);
748 void
749 hist_init(Source *s)
751 struct statfs sf;
753 if (Flag(FTALKING) == 0)
754 return;
756 hstarted = 1;
758 hist_source = s;
760 hname = str_val(global("HISTFILE"));
761 if (hname == NULL || strlen(hname) == 0)
762 return;
763 hname = str_save(hname, APERM);
764 if (statfs(hname, &sf) == 0) {
765 if (!strcmp(sf.f_fstypename, "ffs"))
766 real_fs = 1;
767 ro_fs = sf.f_flags & MNT_RDONLY;
769 if (real_fs == 0)
770 asprintf(&lname, "%s.lock", hname);
771 histfd = history_open();
772 if (histfd == NULL)
773 return;
775 history_load(s);
777 history_unlock();
780 static void
781 writehistfile(void)
783 int i;
784 char *cmd;
786 /* see if file has grown over 125% */
787 if (line_co < histsize + (histsize / 4))
788 return;
790 if (ftruncate(fileno(histfd), 0) == -1)
791 return;
793 /* rewrite the whole kaboodle */
794 rewind(histfd);
795 for (i = 0; i < histsize; i++) {
796 cmd = history[i];
797 if (cmd == NULL)
798 break;
799 if (fprintf(histfd, "%s\n", cmd) == -1)
800 return;
803 line_co = histsize;
805 fflush(histfd);
806 fstat(fileno(histfd), &last_sb);
809 void
810 hist_finish(void)
812 history_close();
814 #else /* HISTORY */
816 /* No history to be compiled in: dummy routines to avoid lots more ifdefs */
817 void
818 init_histvec(void)
821 void
822 hist_init(Source *s)
825 void
826 hist_finish(void)
829 void
830 histsave(int lno, const char *cmd, int dowrite)
832 errorf("history not enabled");
834 #endif /* HISTORY */