From c53643fb2a4f000b55a80c02943c6267662104e2 Mon Sep 17 00:00:00 2001 From: Tanaka Akira Date: Fri, 30 Jul 1999 10:07:46 +0000 Subject: zsh-workers:7321 --- Src/hist.c | 872 ++++++++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 599 insertions(+), 273 deletions(-) diff --git a/Src/hist.c b/Src/hist.c index e104e1f3e..f311ca4a0 100644 --- a/Src/hist.c +++ b/Src/hist.c @@ -30,6 +30,27 @@ #include "zsh.mdh" #include "hist.pro" +/* Functions to call for getting/ungetting a character and for history + * word control. */ + +/**/ +int (*hgetc) _((void)); + +/**/ +void (*hungetc) _((int)); + +/**/ +void (*hwaddc) _((int)); + +/**/ +void (*hwbegin) _((int)); + +/**/ +void (*hwend) _((void)); + +/**/ +void (*addtoline) _((int)); + /* != 0 means history substitution is turned off */ /**/ @@ -56,23 +77,29 @@ int excs, exlast; * Note on curhist: with history inactive, this points to the * last line actually added to the history list. With history active, * the line does not get added to the list until hend(), if at all. - * However, curhist is incremented to reflect the current line anyway. - * Thus if the line is not added to the list, curhist must be - * decremented in hend(). + * However, curhist is incremented to reflect the current line anyway + * and a temporary history entry is inserted while the user is editing. + * If the resulting line was not added to the list, a flag is set so + * that curhist will be decremented in hbegin(). */ /**/ int curhist; -/* number of history entries */ - /**/ -int histentct; - -/* array of history entries */ - +struct histent curline; + +/* current line count of allocated history entries */ + +/**/ +int histlinect; + +/* The history lines are kept in a hash, and also doubly-linked in a ring */ + /**/ -Histent histentarr; +HashTable histtab; +/**/ +Histent hist_ring; /* capacity of history lists */ @@ -90,6 +117,17 @@ int histdone; /**/ int histactive; +/* Current setting of the associated option, but sometimes also includes + * the setting of the HIST_SAVE_NO_DUPS option. */ + +/**/ +int hist_ignore_all_dups; + +/* What flags (if any) we should skip when moving through the history */ + +/**/ +int hist_skip_flags; + /* Bits of histactive variable */ #define HA_ACTIVE (1<<0) /* History mechanism is active */ #define HA_NOSTORE (1<<1) /* Don't store the line when finished */ @@ -142,12 +180,11 @@ int hlinesz; /* default event (usually curhist-1, that is, "!!") */ static int defev; - + /* add a character to the current history word */ -/**/ -void -hwaddc(int c) +static void +ihwaddc(int c) { /* Only if history line exists and lexing has not finished. */ if (chline && !(errflag || lexstop)) { @@ -165,7 +202,7 @@ hwaddc(int c) if (hptr - chline >= hlinesz) { int oldsiz = hlinesz; - chline = realloc(chline, hlinesz = oldsiz + 16); + chline = realloc(chline, hlinesz = oldsiz + 64); hptr = chline + oldsiz; } } @@ -175,12 +212,12 @@ hwaddc(int c) * zsh expands history (see doexpandhist() in zle_tricky.c). It also * * calculates the new cursor position after the expansion. It is called * * from hgetc() and from gettok() in lex.c for characters in comments. */ - + /**/ void -addtoline(int c) +iaddtoline(int c) { - if (! expanding || lexstop) + if (!expanding || lexstop) return; if (qbang && c == bangchar && stophist < 2) { exlast--; @@ -199,9 +236,8 @@ addtoline(int c) line[cs++] = itok(c) ? ztokens[c - Pound] : c; } -/**/ -int -hgetc(void) +static int +ihgetc(void) { int c = ingetc(); @@ -217,7 +253,7 @@ hgetc(void) } if ((inbufflags & INP_HIST) && !stophist) { /* the current character c came from a history expansion * - * (inbufflags && INP_HIST) and history is not disabled * + * (inbufflags & INP_HIST) and history is not disabled * * (e.g. we are not inside single quotes). In that case, \! * * should be treated as ! (since this \! came from a previous * * history line where \ was used to escape the bang). So if * @@ -255,7 +291,7 @@ safeinungetc(int c) void herrflush(void) { - while (!lexstop && inbufct) + while (!lexstop && inbufct && !strin) hwaddc(ingetc()); } @@ -395,7 +431,7 @@ histsubchar(int c) if (!*buf) { if (c != '%') { if (isset(CSHJUNKIEHISTORY)) - ev = curhist - 1; + ev = addhistnum(curhist,-1,HIST_FOREIGN); else ev = defev; if (c == ':' && evset == -1) @@ -410,10 +446,10 @@ histsubchar(int c) evset = 0; } } else if ((t0 = atoi(buf))) { - ev = (t0 < 0) ? curhist + t0 : t0; + ev = (t0 < 0) ? addhistnum(curhist,t0,HIST_FOREIGN) : t0; evset = 1; } else if ((unsigned)*buf == bangchar) { - ev = curhist - 1; + ev = addhistnum(curhist,-1,HIST_FOREIGN); evset = 1; } else if (*buf == '#') { ev = curhist; @@ -589,9 +625,8 @@ histsubchar(int c) /* unget a char and remove it from chline. It can only be used * * to unget a character returned by hgetc. */ -/**/ -void -hungetc(int c) +static void +ihungetc(int c) { int doit = 1; @@ -624,10 +659,10 @@ hungetc(int c) /**/ void -strinbeg(void) +strinbeg(int dohist) { strin++; - hbegin(); + hbegin(dohist); lexinit(); } @@ -644,60 +679,72 @@ strinend(void) histdone = 0; } +/* dummy functions to use instead of hwaddc(), hwbegin(), and hwend() when + * they aren't needed */ + +static void +nohw(int c) +{ +} + +static void +nohwe(void) +{ +} + /* initialize the history mechanism */ /**/ void -hbegin(void) +hbegin(int dohist) { - Histent curhistent; - isfirstln = isfirstch = 1; errflag = histdone = spaceflag = 0; - stophist = (!interact || unset(BANGHIST) || unset(SHINSTDIN)) << 1; - chline = hptr = zcalloc(hlinesz = 16); - chwords = zalloc((chwordlen = 16)*sizeof(short)); + stophist = (!dohist || !interact || unset(SHINSTDIN)) ? 2 : 0; + if (stophist == 2 || (inbufflags & INP_ALIAS)) { + chline = hptr = NULL; + hlinesz = 0; + chwords = NULL; + chwordlen = 0; + hgetc = ingetc; + hungetc = inungetc; + hwaddc = nohw; + hwbegin = nohw; + hwend = nohwe; + addtoline = nohw; + } else { + chline = hptr = zcalloc(hlinesz = 64); + chwords = zalloc((chwordlen = 64) * sizeof(short)); + hgetc = ihgetc; + hungetc = ihungetc; + hwaddc = ihwaddc; + hwbegin = ihwbegin; + hwend = ihwend; + addtoline = iaddtoline; + if (!isset(BANGHIST)) + stophist = 2; + } chwordpos = 0; if (histactive & HA_JUNKED) curhist--; - curhistent = gethistent(curhist); - if (!curhistent->ftim) - curhistent->ftim = time(NULL); - histactive = HA_ACTIVE; + if (hist_ring && !hist_ring->ftim) + hist_ring->ftim = time(NULL); if (interact && isset(SHINSTDIN) && !strin) { + histactive = HA_ACTIVE; attachtty(mypgrp); - defev = curhist; - curhist++; + if (!hist_ring) + hist_ring = curline.up = curline.down = &curline; + else { + curline.up = hist_ring; + curline.down = hist_ring->down; + hist_ring->down = hist_ring->down->up = &curline; + hist_ring = &curline; + } + curline.histnum = ++curhist; + defev = addhistnum(curhist, -1, HIST_FOREIGN); } else - histactive |= HA_NOINC; -} - -/* compare current line with history entry using only text in words */ - -/**/ -static int -histcmp(Histent he) -{ - int kword, lword; - int nwords = chwordpos/2; - - /* If the history entry came from a file, the words were not - * divided by the lexer so we have to resort to strcmp. - */ - if (he->flags & HIST_READ) - return strcmp(he->text, chline); - - if (nwords != he->nwords) - return 1; - - for (kword = 0; kword < 2*nwords; kword += 2) - if ((lword = chwords[kword+1]-chwords[kword]) - != he->words[kword+1]-he->words[kword] || - memcmp(he->text+he->words[kword], chline+chwords[kword], lword)) - return 1; - - return 0; + histactive = HA_ACTIVE | HA_NOINC; } /**/ @@ -726,6 +773,145 @@ histreduceblanks(void) chline[pos] = '\0'; } +/**/ +void +histremovedups(void) +{ + Histent he, next; + for (he = hist_ring; he; he = next) { + next = up_histent(he); + if (he->flags & HIST_DUP) + freehistnode((HashNode)he); + } +} + +/**/ +int +addhistnum(int hl, int n, int xflags) +{ + int dir = n < 0? -1 : n > 0? 1 : 0; + Histent he = gethistent(hl, dir); + + if (!he) + return 0; + if (he->histnum != hl) + n -= dir; + if (n) + he = movehistent(he, n, xflags); + if (!he) + return dir < 0? firsthist() : curhist; + return he->histnum; +} + +/**/ +Histent +movehistent(Histent he, int n, int xflags) +{ + while (n < 0) { + if (!(he = up_histent(he))) + return NULL; + if (!(he->flags & xflags)) + n++; + } + while (n > 0) { + if (!(he = down_histent(he))) + return NULL; + if (!(he->flags & xflags)) + n--; + } + return he; +} + +/**/ +Histent +up_histent(Histent he) +{ + return he->up == hist_ring? NULL : he->up; +} + +/**/ +Histent +down_histent(Histent he) +{ + return he == hist_ring? NULL : he->down; +} + +/**/ +Histent +gethistent(int ev, int nearmatch) +{ + Histent he; + + if (!hist_ring) + return NULL; + + if (ev - hist_ring->down->histnum < hist_ring->histnum - ev) { + for (he = hist_ring->down; he->histnum <= ev; he = he->down) { + if (he->histnum == ev) + return he; + } + if (nearmatch < 0) + return up_histent(he); + if (nearmatch > 0) + return he; + } + else { + for (he = hist_ring; he->histnum >= ev; he = he->up) { + if (he->histnum == ev) + return he; + } + if (nearmatch < 0) + return he; + if (nearmatch > 0) + return down_histent(he); + } + + return NULL; +} + +/**/ +Histent +prepnexthistent(int histnum) +{ + Histent he; + + if (histlinect < histsiz) { + he = (Histent)zcalloc(sizeof *he); + if (!hist_ring) + hist_ring = he->up = he->down = he; + else { + he->up = hist_ring; + he->down = hist_ring->down; + hist_ring->down = he->down->up = he; + hist_ring = he; + } + histlinect++; + } + else { + he = hist_ring->down; + if (isset(HISTEXPIREDUPSFIRST) && !(he->flags & HIST_DUP)) { + int max_unique_ct = getiparam("SAVEHIST"); + do { + if (max_unique_ct-- <= 0) { + he = hist_ring->down; + break; + } + he = he->down; + } while (he != hist_ring->down && !(he->flags & HIST_DUP)); + if (he != hist_ring->down) { + he->up->down = he->down; + he->down->up = he->up; + he->up = hist_ring; + he->down = hist_ring->down; + hist_ring->down = he->down->up = he; + } + } + freehistdata(hist_ring = he, 0); + } + hist_ring->histnum = histnum; + return hist_ring; +} + /* say we're done using the history mechanism */ /**/ @@ -733,19 +919,36 @@ int hend(void) { int flag, save = 1; + char *hf = getsparam("HISTFILE"); - DPUTS(!chline, "BUG: chline is NULL in hend()"); + DPUTS(stophist != 2 && !(inbufflags & INP_ALIAS) && !chline, + "BUG: chline is NULL in hend()"); if (histdone & HISTFLAG_SETTY) settyinfo(&shttyinfo); + if (!(histactive & HA_NOINC)) { + curline.up->down = curline.down; + curline.down->up = curline.up; + if (hist_ring == &curline) { + if (!histlinect) + hist_ring = NULL; + else + hist_ring = curline.up; + } + curhist--; + } if (histactive & (HA_NOSTORE|HA_NOINC)) { zfree(chline, hlinesz); zfree(chwords, chwordlen*sizeof(short)); chline = NULL; - if (!(histactive & HA_NOINC)) - curhist--; histactive = 0; return 1; } + if (hist_ignore_all_dups != isset(HISTIGNOREALLDUPS) + && (hist_ignore_all_dups = isset(HISTIGNOREALLDUPS)) != 0) + histremovedups(); + /* For history sharing, lock history file once for both read and write */ + if (isset(SHAREHISTORY) && lockhistfile(hf, 0)) + readhistfile(hf, 0, HFILE_USE_OPTIONS | HFILE_FAST); flag = histdone; histdone = 0; if (hptr < chline + 1) @@ -781,8 +984,10 @@ hend(void) } if (save) { Histent he; - int keepflags = 0; + int keepflags; + for (he = hist_ring; he && he->flags & hist_skip_flags; + he = up_histent(he)) ; #ifdef DEBUG /* debugging only */ if (chwordpos%2) { @@ -791,28 +996,26 @@ hend(void) } #endif /* get rid of pesky \n which we've already nulled out */ - if (!chline[chwords[chwordpos-2]]) + if (chwordpos > 1 && !chline[chwords[chwordpos-2]]) chwordpos -= 2; /* strip superfluous blanks, if desired */ if (isset(HISTREDUCEBLANKS)) histreduceblanks(); - - if (isset(HISTIGNOREDUPS) && (he = gethistent(curhist - 1)) - && he->text && !histcmp(he)) { + if ((isset(HISTIGNOREDUPS) || isset(HISTIGNOREALLDUPS)) && he + && histstrcmp(chline, he->text) == 0) { /* This history entry compares the same as the previous. * In case minor changes were made, we overwrite the - * previous one with the current one. This also gets - * the timestamp right. However, keep the old flags. + * previous one with the current one. This also gets the + * timestamp right. Perhaps, preserve the HIST_OLD flag. */ - keepflags = he->flags; - curhist--; + keepflags = he->flags & HIST_OLD; /* Avoid re-saving */ + freehistdata(he, 0); + } else { + keepflags = 0; + he = prepnexthistent(++curhist); } - he = gethistent(curhist); - zsfree(he->text); he->text = ztrdup(chline); - if (he->nwords) - zfree(he->words, he->nwords*2*sizeof(short)); he->stim = time(NULL); he->ftim = 0L; he->flags = keepflags; @@ -821,12 +1024,15 @@ hend(void) he->words = (short *)zalloc(chwordpos * sizeof(short)); memcpy(he->words, chwords, chwordpos * sizeof(short)); } - } else - curhist--; + addhistnode(histtab, he->text, he); + } zfree(chline, hlinesz); zfree(chwords, chwordlen*sizeof(short)); chline = NULL; histactive = 0; + if (isset(SHAREHISTORY) || isset(INCAPPENDHISTORY)) + savehistfile(hf, 1, HFILE_USE_OPTIONS | HFILE_FAST); + unlockhistfile(hf); /* It's OK to call this even if we aren't locked */ return !(flag & HISTFLAG_NOEXEC || errflag); } @@ -836,12 +1042,11 @@ hend(void) void remhist(void) { + if (hist_ring == &curline) + return; if (!(histactive & HA_ACTIVE)) { if (!(histactive & HA_JUNKED)) { - /* make sure this doesn't show up when we do firsthist() */ - Histent he = gethistent(curhist); - zsfree(he->text); - he->text = NULL; + freehistnode((HashNode)hist_ring); histactive |= HA_JUNKED; /* curhist-- is delayed until the next hbegin() */ } @@ -858,8 +1063,10 @@ int hwgetword = -1; /**/ void -hwbegin(int offset) +ihwbegin(int offset) { + if (stophist == 2 || strin) + return; if (chwordpos%2) chwordpos--; /* make sure we're on a word start, not end */ /* If we're expanding an alias, we should overwrite the expansion @@ -876,15 +1083,18 @@ hwbegin(int offset) /**/ void -hwend(void) +ihwend(void) { + if (stophist == 2 || strin) + return; if (chwordpos%2 && chline) { /* end of word reached and we've already begun a word */ if (hptr > chline + chwords[chwordpos-1]) { chwords[chwordpos++] = hptr - chline; if (chwordpos >= chwordlen) { chwords = (short *) realloc(chwords, - (chwordlen += 16)*sizeof(short)); + (chwordlen += 32) * + sizeof(short)); } if (hwgetword > -1) { /* We want to reuse the current word position */ @@ -1026,18 +1236,21 @@ getargspec(int argc, int marg, int evset) static int hconsearch(char *str, int *marg) { - int t0, t1 = 0; + int t1 = 0; char *s; Histent he; - for (t0 = curhist - 1; (he = quietgethist(t0)); t0--) + for (he = up_histent(hist_ring); he; he = up_histent(he)) { + if (he->flags & HIST_FOREIGN) + continue; if ((s = strstr(he->text, str))) { int pos = s - he->text; while (t1 < he->nwords && he->words[2*t1] <= pos) t1++; *marg = t1 - 1; - return t0; + return he->histnum; } + } return -1; } @@ -1047,12 +1260,15 @@ hconsearch(char *str, int *marg) int hcomsearch(char *str) { - int t0; - char *hs; + Histent he; + int len = strlen(str); - for (t0 = curhist - 1; (hs = quietgetevent(t0)); t0--) - if (!strncmp(hs, str, strlen(str))) - return t0; + for (he = up_histent(hist_ring); he; he = up_histent(he)) { + if (he->flags & HIST_FOREIGN) + continue; + if (strncmp(he->text, str, len) == 0) + return he->histnum; + } return -1; } @@ -1206,33 +1422,15 @@ convamps(char *out, char *in, int inlen) } /**/ -struct histent * +Histent quietgethist(int ev) { - static struct histent storehist; - - if (ev < firsthist() || ev > curhist) - return NULL; if (ev == curhist && (histactive & HA_ACTIVE)) { - /* The current history line has not been stored. Build it up - * from other variables. - */ - storehist.text = chline; - storehist.nwords = chwordpos/2; - storehist.words = chwords; - - return &storehist; - } else - return gethistent(ev); -} - -/**/ -char * -quietgetevent(int ev) -{ - Histent ent = quietgethist(ev); - - return ent ? ent->text : NULL; + curline.text = chline; + curline.nwords = chwordpos/2; + curline.words = chwords; + } + return gethistent(ev, GETHIST_EXACT); } /**/ @@ -1362,10 +1560,9 @@ quotebreak(char **tr) return 0; } -#if 0 /* read an arbitrary amount of data into a buffer until stop is found */ -/**/ +#if 0 /**/ char * hdynread(int stop) { @@ -1424,61 +1621,80 @@ hdynread2(int stop) void inithist(void) { - histentct = histsiz; - histentarr = (Histent) zcalloc(histentct * sizeof *histentarr); + createhisttable(); } /**/ void resizehistents(void) { - int newentct, t0, t1, firstlex; - Histent newarr; - - newentct = histsiz; - newarr = (Histent) zcalloc(newentct * sizeof *newarr); - firstlex = curhist - histsiz + 1; - t0 = firsthist(); - if (t0 < curhist - newentct) - t0 = curhist - newentct; - t1 = t0 % newentct; - for (; t0 <= curhist; t0++) { - newarr[t1] = *gethistent(t0); - if (t0 < firstlex) { - zsfree(newarr[t1].text); - newarr[t1].text = NULL; - } - t1++; - if (t1 == newentct) - t1 = 0; - } - free(histentarr); - histentarr = newarr; - histentct = newentct; + while (histlinect > histsiz) + freehistnode((HashNode)hist_ring->down); } +/* Remember the last line in the history file so we can find it again. */ +static struct { + char *text; + time_t stim, mtim; + off_t fpos, fsiz; + int next_write_ev; +} lasthist; + +static int histfile_linect; + /**/ void -readhistfile(char *s, int err) +readhistfile(char *fn, int err, int readflags) { - char *buf; + char *buf, *start = NULL; FILE *in; - Histent ent; - time_t tim = time(NULL); + Histent he; + time_t stim, ftim, tim = time(NULL); + off_t fpos; short *wordlist; + struct stat sb; int nwordpos, nwordlist, bufsiz; + int searching, newflags; - if (!s) + if (!fn && !(fn = getsparam("HISTFILE"))) + return; + if (readflags & HFILE_FAST) { + if (stat(fn, &sb) < 0 + || (lasthist.fsiz == sb.st_size && lasthist.mtim == sb.st_mtime) + || !lockhistfile(fn, 0)) + return; + lasthist.fsiz = sb.st_size; + lasthist.mtim = sb.st_mtime; + } + else if (!lockhistfile(fn, 1)) return; - if ((in = fopen(unmeta(s), "r"))) { - nwordlist = 16; + if ((in = fopen(unmeta(fn), "r"))) { + nwordlist = 64; wordlist = (short *)zalloc(nwordlist*sizeof(short)); bufsiz = 1024; buf = zalloc(bufsiz); - while (fgets(buf, bufsiz, in)) { + if (readflags & HFILE_FAST && lasthist.text) { + if (lasthist.fpos < lasthist.fsiz) { + fseek(in, lasthist.fpos, 0); + searching = 1; + } + else { + histfile_linect = 0; + searching = -1; + } + } else + searching = 0; + + newflags = HIST_OLD | HIST_READ; + if (readflags & HFILE_FAST) + newflags |= HIST_FOREIGN; + if (readflags & HFILE_SKIPOLD + || (hist_ignore_all_dups && newflags & hist_skip_flags)) + newflags |= HIST_MAKEUNIQUE; + while (fpos = ftell(in), fgets(buf, bufsiz, in)) { int l = strlen(buf); - char *pt, *start; + char *pt; while (l) { while (buf[l - 1] != '\n') { @@ -1488,114 +1704,184 @@ readhistfile(char *s, int err) l++; break; } - l = strlen(buf); + l += strlen(buf+l); } buf[l - 1] = '\0'; if (l > 1 && buf[l - 2] == '\\') { - buf[l - 2] = '\n'; - fgets(buf + l - 1, bufsiz - (l - 1), in); - l = strlen(buf); + buf[--l - 1] = '\n'; + fgets(buf + l, bufsiz - l, in); + l += strlen(buf+l); } else break; } - ent = gethistent(++curhist); pt = buf; if (*pt == ':') { pt++; - ent->stim = zstrtol(pt, NULL, 0); + stim = zstrtol(pt, NULL, 0); for (; *pt != ':' && *pt; pt++); if (*pt) { pt++; - ent->ftim = zstrtol(pt, NULL, 0); + ftim = zstrtol(pt, NULL, 0); for (; *pt != ';' && *pt; pt++); if (*pt) pt++; - } else { - ent->ftim = tim; - } - if (ent->stim == 0) - ent->stim = tim; - if (ent->ftim == 0) - ent->ftim = tim; + } else + ftim = stim; } else { - ent->ftim = ent->stim = tim; + if (*pt == '\\' && pt[1] == ':') + pt++; + stim = ftim = 0; } - zsfree(ent->text); - ent->text = ztrdup(pt); - ent->flags = HIST_OLD|HIST_READ; - if (ent->nwords) - zfree(ent->words, ent->nwords*2*sizeof(short)); + if (searching) { + if (searching > 0) { + if (stim == lasthist.stim + && histstrcmp(pt, lasthist.text) == 0) + searching = 0; + else { + fseek(in, 0, 0); + histfile_linect = 0; + searching = -1; + } + continue; + } + else if (stim < lasthist.stim) { + histfile_linect++; + continue; + } + searching = 0; + } + + if (readflags & HFILE_USE_OPTIONS) { + histfile_linect++; + lasthist.fpos = fpos; + lasthist.stim = stim; + } + + he = prepnexthistent(++curhist); + he->text = ztrdup(pt); + he->flags = newflags; + if ((he->stim = stim) == 0) + he->stim = he->ftim = tim; + else if (ftim < stim) + he->ftim = stim + ftim; + else + he->ftim = ftim; /* Divide up the words. We don't know how it lexes, - so just look for spaces. + so just look for white-space. */ nwordpos = 0; start = pt; do { - while (*pt == ' ') + while (inblank(*pt)) pt++; if (*pt) { if (nwordpos >= nwordlist) wordlist = (short *) realloc(wordlist, - (nwordlist += 16)*sizeof(short)); + (nwordlist += 64)*sizeof(short)); wordlist[nwordpos++] = pt - start; - while (*pt && *pt != ' ') + while (*pt && !inblank(*pt)) pt++; wordlist[nwordpos++] = pt - start; } } while (*pt); - ent->nwords = nwordpos/2; - if (ent->nwords) { - ent->words = (short *)zalloc(nwordpos*sizeof(short)); - memcpy(ent->words, wordlist, nwordpos*sizeof(short)); + he->nwords = nwordpos/2; + if (he->nwords) { + he->words = (short *)zalloc(nwordpos*sizeof(short)); + memcpy(he->words, wordlist, nwordpos*sizeof(short)); } else - ent->words = (short *)NULL; + he->words = (short *)NULL; + addhistnode(histtab, he->text, he); + if (hist_ring != he) + curhist--; /* We discarded a foreign duplicate */ + } + if (start && readflags & HFILE_USE_OPTIONS) { + zsfree(lasthist.text); + lasthist.text = ztrdup(start); } - fclose(in); - zfree(wordlist, nwordlist*sizeof(short)); zfree(buf, bufsiz); + + fclose(in); } else if (err) - zerr("can't read history file", s, 0); + zerr("can't read history file", fn, 0); + + unlockhistfile(fn); } /**/ void -savehistfile(char *s, int err, int app) +savehistfile(char *fn, int err, int writeflags) { - char *t; + char *t, *start = NULL; FILE *out; - int ev; - Histent ent; + Histent he; + int xcurhist = curhist - !!(histactive & HA_ACTIVE); int savehist = getiparam("SAVEHIST"); + int extended_history = isset(EXTENDEDHISTORY); - if (!s || !interact || savehist <= 0) + if (!interact || savehist <= 0 || !hist_ring + || (!fn && !(fn = getsparam("HISTFILE")))) return; - ev = curhist - savehist + 1; - if (ev < firsthist()) - ev = firsthist(); - if (app & 1) - out = fdopen(open(unmeta(s), - O_CREAT | O_WRONLY | O_APPEND | O_NOCTTY, 0600), "a"); - else - out = fdopen(open(unmeta(s), - O_CREAT | O_WRONLY | O_TRUNC | O_NOCTTY, 0600), "w"); + if (writeflags & HFILE_FAST) { + he = gethistent(lasthist.next_write_ev, GETHIST_DOWNWARD); + while (he && he->flags & HIST_OLD) { + lasthist.next_write_ev = he->histnum + 1; + he = down_histent(he); + } + if (!he || !lockhistfile(fn, 0)) + return; + if (histfile_linect > savehist + savehist / 5) + writeflags &= ~HFILE_FAST; + } + else { + if (!lockhistfile(fn, 1)) + return; + he = hist_ring->down; + } + if (writeflags & HFILE_USE_OPTIONS) { + if (isset(APPENDHISTORY) || isset(INCAPPENDHISTORY) + || isset(SHAREHISTORY)) + writeflags |= HFILE_APPEND | HFILE_SKIPOLD; + else + histfile_linect = 0; + if (isset(HISTSAVENODUPS)) + writeflags |= HFILE_SKIPDUPS; + if (isset(SHAREHISTORY)) + extended_history = 1; + } + if (writeflags & HFILE_APPEND) { + out = fdopen(open(unmeta(fn), + O_CREAT | O_WRONLY | O_APPEND | O_NOCTTY, 0600), "a"); + } + else { + out = fdopen(open(unmeta(fn), + O_CREAT | O_WRONLY | O_TRUNC | O_NOCTTY, 0600), "w"); + } if (out) { - for (; ev <= curhist - !!(histactive & HA_ACTIVE); ev++) { - ent = gethistent(ev); - if (app & 2) { - if (ent->flags & HIST_OLD) + for (; he && he->histnum <= xcurhist; he = down_histent(he)) { + if ((writeflags & HFILE_SKIPDUPS && he->flags & HIST_DUP) + || (writeflags & HFILE_SKIPFOREIGN && he->flags & HIST_FOREIGN)) + continue; + if (writeflags & HFILE_SKIPOLD) { + if (he->flags & HIST_OLD) continue; - ent->flags |= HIST_OLD; + he->flags |= HIST_OLD; + if (writeflags & HFILE_USE_OPTIONS) + lasthist.next_write_ev = he->histnum + 1; } - t = ent->text; - if (isset(EXTENDEDHISTORY)) { - fprintf(out, ": %ld:%ld;", - (long)ent->stim, - (long)ent->ftim); + if (writeflags & HFILE_USE_OPTIONS) { + lasthist.fpos = ftell(out); + lasthist.stim = he->stim; + histfile_linect++; + } + t = start = he->text; + if (extended_history) { + fprintf(out, ": %ld:%ld;", (long)he->stim, + he->ftim? (long)(he->ftim - he->stim) : 0L); } else if (*t == ':') fputc('\\', out); @@ -1606,69 +1892,109 @@ savehistfile(char *s, int err, int app) } fputc('\n', out); } + if (start && writeflags & HFILE_USE_OPTIONS) { + struct stat sb; + fflush(out); + if (fstat(fileno(out), &sb) == 0) { + lasthist.fsiz = sb.st_size; + lasthist.mtim = sb.st_mtime; + } + zsfree(lasthist.text); + lasthist.text = ztrdup(start); + } fclose(out); - if (app & 2 && (out = fopen(unmeta(s), "r"))) { - char **store, buf[1024], **ptr; - int i, l, histnum = 0; - - store = (char **)zcalloc((savehist + 1) * sizeof *store); - while (fgets(buf, sizeof(buf), out)) { - char *t; - - if (store[i = histnum % savehist]) - free(store[i]); - store[i] = ztrdup(buf); - l = strlen(buf); - if (l > 1) { - t = store[i] + l; - while ((t[-1] != '\n' || - (t[-1] == '\n' && t[-2] == '\\')) && - fgets(buf, sizeof(buf), out)) { - l += strlen(buf); - store[i] = zrealloc(store[i], l + 1); - t = store[i] + l; - strcat(store[i], buf); - } - } - histnum++; - } - fclose(out); - if ((out = fdopen(open(unmeta(s), - O_WRONLY | O_TRUNC | O_NOCTTY, 0600), "w"))) { - if (histnum < savehist) - for (i = 0; i < histnum; i++) - fprintf(out, "%s", store[i]); - else - for (i = histnum; i < histnum + savehist; i++) - fprintf(out, "%s", store[i % savehist]); - fclose(out); - } - for (ptr = store; *ptr; ptr++) - zsfree(*ptr); - free(store); + if ((writeflags & (HFILE_SKIPOLD | HFILE_FAST)) == HFILE_SKIPOLD) { + HashTable remember_histtab = histtab; + Histent remember_hist_ring = hist_ring; + int remember_histlinect = histlinect; + int remember_curhist = curhist; + + hist_ring = NULL; + curhist = histlinect = 0; + histsiz = savehist; + createhisttable(); /* sets histtab */ + + hist_ignore_all_dups |= isset(HISTSAVENODUPS); + readhistfile(fn, err, 0); + hist_ignore_all_dups = isset(HISTIGNOREALLDUPS); + savehistfile(fn, err, 0); + deletehashtable(histtab); + + curhist = remember_curhist; + histlinect = remember_histlinect; + hist_ring = remember_hist_ring; + histtab = remember_histtab; } } else if (err) - zerr("can't write history file %s", s, 0); + zerr("can't write history file %s", fn, 0); + + unlockhistfile(fn); } +static int lockhistct; + /**/ int -firsthist(void) +lockhistfile(char *fn, int keep_trying) { - int ev; - Histent ent; + int ct = lockhistct; - ev = curhist - histentct + 1; - if (ev < 1) - ev = 1; - do { - ent = gethistent(ev); - if (ent->text) - break; - ev++; + if (!fn && !(fn = getsparam("HISTFILE"))) + return 0; + if (!lockhistct++) { + struct stat sb; + int fd, len = strlen(fn); + char *tmpfile, *lockfile; + + tmpfile = zalloc(len + 10 + 1); + sprintf(tmpfile, "%s.%ld", fn, (long)mypid); + if ((fd = open(tmpfile, O_RDWR|O_CREAT|O_EXCL, 0644)) >= 0) { + write(fd, "0\n", 2); + close(fd); + lockfile = zalloc(len + 5 + 1); + sprintf(lockfile, "%s.LOCK", fn); + while (link(tmpfile, lockfile) < 0) { + if (stat(lockfile, &sb) < 0) { + if (errno == ENOENT) + continue; + } + else if (keep_trying) { + if (time(NULL) - sb.st_mtime < 10) + sleep(1); + else + unlink(lockfile); + continue; + } + lockhistct--; + break; + } + free(lockfile); + } + unlink(tmpfile); + free(tmpfile); } - while (ev < curhist); - return ev; + return ct != lockhistct; } +/* Unlock the history file if this corresponds to the last nested lock + * request. If we don't have the file locked, just return. + */ + +/**/ +void +unlockhistfile(char *fn) +{ + if (!fn && !(fn = getsparam("HISTFILE"))) + return; + if (--lockhistct) { + if (lockhistct < 0) + lockhistct = 0; + } + else { + char *lockfile = zalloc(strlen(fn) + 5 + 1); + sprintf(lockfile, "%s.LOCK", fn); + unlink(lockfile); + free(lockfile); + } +} -- cgit 1.4.1