1 /* $OpenBSD: history.c,v 1.80 2018/01/15 22:30:38 jca Exp $ */
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
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);
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
;
55 struct temp
*tf
= NULL
;
56 char *p
, *editor
= NULL
;
57 int gflag
= 0, lflag
= 0, nflag
= 0, sflag
= 0, rflag
= 0;
59 char *first
= NULL
, *last
= NULL
;
60 char **hfirst
, **hlast
, **hp
;
64 bi_errorf("history function called recursively");
68 if (!Flag(FTALKING_I
)) {
69 bi_errorf("history functions not available");
73 while ((optc
= ksh_getopt(wp
, &builtin_opt
,
74 "e:glnrs0,1,2,3,4,5,6,7,8,9,")) != -1)
77 p
= builtin_opt
.optarg
;
78 if (strcmp(p
, "-") == 0)
81 size_t len
= strlen(p
) + 4;
82 editor
= str_nsave(p
, len
, ATEMP
);
83 strlcat(editor
, " $_", len
);
86 case 'g': /* non-at&t ksh */
98 case 's': /* posix version of -e - */
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
);
111 bi_errorf("too many arguments");
118 wp
+= builtin_opt
.optind
;
120 /* Substitute and execute command */
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 -)");
129 /* Check for pattern replacement argument */
130 if (*wp
&& **wp
&& (p
= strchr(*wp
+ 1, '='))) {
131 pat
= str_save(*wp
, ATEMP
);
137 /* Check for search prefix */
138 if (!first
&& (first
= *wp
))
141 bi_errorf("too many arguments");
145 hp
= first
? hist_get(first
, false, false) :
146 hist_get_newest(false);
150 ret
= hist_replace(hp
, pat
, rep
, gflag
);
155 if (editor
&& (lflag
|| nflag
)) {
156 bi_errorf("can't use -l, -n with -e");
160 if (!first
&& (first
= *wp
))
162 if (!last
&& (last
= *wp
))
165 bi_errorf("too many arguments");
169 hfirst
= lflag
? hist_get("-16", true, true) :
170 hist_get_newest(false);
173 /* can't fail if hfirst didn't fail */
174 hlast
= hist_get_newest(false);
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);
184 hlast
= last
? hist_get(last
, true, lflag
? true : false) :
185 (lflag
? hist_get_newest(false) : hfirst
);
189 if (hfirst
> hlast
) {
192 temp
= hfirst
; hfirst
= hlast
; hlast
= temp
;
193 rflag
= !rflag
; /* POSIX */
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
);
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
));
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
));
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);
249 if (!(shf
= shf_open(tf
->name
, O_RDONLY
, 0, 0))) {
250 bi_errorf("cannot open temp file %s", tf
->name
);
254 n
= fstat(shf
->fd
, &statb
) < 0 ? 128 :
256 Xinit(xs
, xp
, n
, hist_source
->areap
);
257 while ((n
= shf_read(xp
, Xnleft(xs
, xp
), shf
)) > 0) {
259 if (Xnleft(xs
, xp
) <= 0)
260 XcheckN(xs
, xp
, Xlength(xs
, xp
));
263 bi_errorf("error reading temp file %s - %s",
264 tf
->name
, strerror(shf
->errno_
));
270 strip_nuls(Xstring(xs
, xp
), Xlength(xs
, xp
));
272 ret
= hist_execute(Xstring(xs
, xp
));
278 /* Save cmd in history, execute cmd (cmd gets trashed) */
280 hist_execute(char *cmd
)
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 */
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) */
301 /* Commands are executed here instead of pushing them onto the
302 * input 'cause posix says the redirection and variable assignments
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.. */
309 ret
= command(cmd
, 0);
315 hist_replace(char **hp
, const char *pat
, const char *rep
, int global
)
320 line
= str_save(*hp
, ATEMP
);
323 int pat_len
= strlen(pat
);
324 int rep_len
= strlen(rep
);
330 Xinit(xs
, xp
, 128, ATEMP
);
331 for (s
= *hp
; (s1
= strstr(s
, pat
)) && (!any_subst
|| global
);
335 XcheckN(xs
, xp
, len
+ rep_len
);
336 memcpy(xp
, s
, len
); /* first part */
338 memcpy(xp
, rep
, rep_len
); /* replacement */
342 bi_errorf("substitution failed");
346 XcheckN(xs
, 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
359 hist_get(const char *str
, int approx
, int allow_cur
)
365 hp
= histptr
+ (n
< 0 ? n
: (n
- hist_source
->line
));
366 if ((long)hp
< (long)history
) {
368 hp
= hist_get_oldest();
370 bi_errorf("%s: not in history", str
);
373 } else if (hp
> histptr
) {
375 hp
= hist_get_newest(allow_cur
);
377 bi_errorf("%s: not in history", str
);
380 } else if (!allow_cur
&& hp
== histptr
) {
381 bi_errorf("%s: invalid range", str
);
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
);
390 bi_errorf("%s: not in history", str
);
398 /* Return a pointer to the newest command in the history */
400 hist_get_newest(int allow_cur
)
402 if (histptr
< history
|| (!allow_cur
&& histptr
== history
)) {
403 bi_errorf("no history (yet)");
411 /* Return a pointer to the oldest command in the history */
413 hist_get_oldest(void)
415 if (histptr
<= history
) {
416 bi_errorf("no history (yet)");
422 /******************************/
423 /* Back up over last histsave */
424 /******************************/
428 static int last_line
= -1;
430 if (histptr
>= history
&& last_line
!= hist_source
->line
) {
432 afree(*histptr
, APERM
);
434 last_line
= hist_source
->line
;
443 for (hp
= history
; hp
<= histptr
; hp
++)
446 histptr
= history
- 1;
447 hist_source
->line
= 0;
451 * Return the current position.
462 int last
= histptr
- history
;
464 if (n
< 0 || n
>= last
) {
468 current
= &history
[n
];
474 * This will become unnecessary if hist_get is modified to allow
475 * searching from positions other than the end, and in either
479 findhist(int start
, int fwd
, const char *str
, int anchored
)
482 int maxhist
= histptr
- history
;
483 int incr
= fwd
? 1 : -1;
484 int len
= strlen(str
);
486 if (start
< 0 || 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
)))
499 findhistrel(const char *str
)
501 int maxhist
= histptr
- history
;
502 int start
= maxhist
- 1;
514 return start
+ rec
+ 1;
518 sethistcontrol(const char *str
)
520 char *spec
, *tok
, *state
;
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)
533 else if (strcmp(tok
, "ignorespace") == 0)
541 * this means reallocating the dataspace
546 if (n
> 0 && (uint32_t)n
!= histsize
) {
547 int offset
= histptr
- history
;
549 /* save most recent history */
550 if (offset
> n
- 1) {
554 for (hp
= history
; hp
< histptr
- offset
; hp
++)
556 memmove(history
, histptr
- offset
, n
* sizeof(char *));
560 histbase
= areallocarray(histbase
, n
+ 1, sizeof(char *), APERM
);
561 history
= histbase
+ 1;
562 histptr
= history
+ offset
;
568 * This can mean reloading/resetting/starting history file
572 sethistfile(const char *name
)
574 /* if not started then nothing to do */
578 /* if the name is the same as the name we have */
579 if (hname
&& strcmp(hname
, name
) == 0)
582 * its a new name - possibly
591 hist_init(hist_source
);
595 * initialise the history vector
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 *),
609 history
= histbase
+ 1;
610 histptr
= history
- 1;
615 history_lock(int operation
)
617 while (flock(fileno(histfh
), operation
) != 0) {
618 if (errno
== EINTR
|| errno
== EAGAIN
)
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
638 histsave(int lno
, const char *cmd
, int dowrite
)
642 if (ignorespace
&& cmd
[0] == ' ')
645 c
= str_save(cmd
, APERM
);
646 if ((cp
= strrchr(c
, '\n')) != NULL
)
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) {
658 if (dowrite
&& histfh
) {
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 */
668 history_load(hist_source
);
674 if (histptr
< history
+ histsize
- 1)
676 else { /* remove oldest command */
677 afree(*history
, APERM
);
678 memmove(history
, history
+ 1,
679 (histsize
- 1) * sizeof(*history
));
683 if (dowrite
&& histfh
) {
688 if (fseeko(histfh
, 0, SEEK_END
) == 0 &&
689 stravis(&encoded
, c
, VIS_SAFE
| VIS_NL
) != -1) {
690 fprintf(histfh
, "%s\n", encoded
);
692 fstat(fileno(histfh
), &last_sb
);
697 history_lock(LOCK_UN
);
710 if ((fd
= open(hname
, O_RDWR
| O_CREAT
, 0600)) == -1)
712 if (flock(fd
, LOCK_EX
) == -1 ||
713 fstat(fd
, &sb
) == -1 || sb
.st_uid
!= getuid()) {
721 if ((f
= fdopen(fddup
, "r+")) == NULL
)
740 history_load(Source
*s
)
742 char *p
, encoded
[LINE
+ 1], line
[LINE
+ 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 */
753 /* maybe a missing trailing newline? */
754 if (strlen(encoded
) != sizeof(encoded
) - 1) {
755 bi_errorf("history file is corrupt");
758 } while (fgets(encoded
, sizeof(encoded
), histfh
)
759 && strchr(encoded
, '\n') == NULL
);
763 bi_errorf("ignored history line(s) longer than"
771 s
->cmd_offset
= line_co
;
772 strunvis(line
, encoded
);
773 histsave(line_co
, line
, 0);
786 int oldmagic1
, oldmagic2
;
788 if (Flag(FTALKING
) == 0)
795 if (str_val(global("HISTFILE")) == null
)
797 hname
= str_save(str_val(global("HISTFILE")), APERM
);
798 histfh
= history_open();
802 oldmagic1
= fgetc(histfh
);
803 oldmagic2
= fgetc(histfh
);
805 if (oldmagic1
== EOF
|| oldmagic2
== EOF
) {
806 if (!feof(histfh
) && ferror(histfh
)) {
810 } else if (oldmagic1
== HMAGIC1
&& oldmagic2
== HMAGIC2
) {
811 bi_errorf("ignoring old style history file");
818 history_lock(LOCK_UN
);
826 /* see if file has grown over 25% */
827 if (line_co
< histsize
+ (histsize
/ 4))
830 /* rewrite the whole caboodle */
832 if (ftruncate(fileno(histfh
), 0) == -1) {
833 bi_errorf("failed to rewrite history file - %s",
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) {
849 fstat(fileno(histfh
), &last_sb
);