1c80476e4SDavid E. O'Brien /*
2c80476e4SDavid E. O'Brien * sh.hist.c: Shell history expansions and substitutions
3c80476e4SDavid E. O'Brien */
4c80476e4SDavid E. O'Brien /*-
5c80476e4SDavid E. O'Brien * Copyright (c) 1980, 1991 The Regents of the University of California.
6c80476e4SDavid E. O'Brien * All rights reserved.
7c80476e4SDavid E. O'Brien *
8c80476e4SDavid E. O'Brien * Redistribution and use in source and binary forms, with or without
9c80476e4SDavid E. O'Brien * modification, are permitted provided that the following conditions
10c80476e4SDavid E. O'Brien * are met:
11c80476e4SDavid E. O'Brien * 1. Redistributions of source code must retain the above copyright
12c80476e4SDavid E. O'Brien * notice, this list of conditions and the following disclaimer.
13c80476e4SDavid E. O'Brien * 2. Redistributions in binary form must reproduce the above copyright
14c80476e4SDavid E. O'Brien * notice, this list of conditions and the following disclaimer in the
15c80476e4SDavid E. O'Brien * documentation and/or other materials provided with the distribution.
1629301572SMark Peek * 3. Neither the name of the University nor the names of its contributors
17c80476e4SDavid E. O'Brien * may be used to endorse or promote products derived from this software
18c80476e4SDavid E. O'Brien * without specific prior written permission.
19c80476e4SDavid E. O'Brien *
20c80476e4SDavid E. O'Brien * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
21c80476e4SDavid E. O'Brien * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22c80476e4SDavid E. O'Brien * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23c80476e4SDavid E. O'Brien * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
24c80476e4SDavid E. O'Brien * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25c80476e4SDavid E. O'Brien * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26c80476e4SDavid E. O'Brien * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27c80476e4SDavid E. O'Brien * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28c80476e4SDavid E. O'Brien * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29c80476e4SDavid E. O'Brien * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30c80476e4SDavid E. O'Brien * SUCH DAMAGE.
31c80476e4SDavid E. O'Brien */
32c80476e4SDavid E. O'Brien #include "sh.h"
3319d2e3deSDmitry Chagin #include <stdio.h> /* for rename(2), grr. */
349ccc37e3SMark Peek #include <assert.h>
35c80476e4SDavid E. O'Brien #include "tc.h"
3619d2e3deSDmitry Chagin #include "dotlock.h"
37c80476e4SDavid E. O'Brien
3823338178SMark Peek extern int histvalid;
3945e5710bSMark Peek extern struct Strbuf histline;
40c80476e4SDavid E. O'Brien Char HistLit = 0;
41c80476e4SDavid E. O'Brien
4245e5710bSMark Peek static int heq (const struct wordent *, const struct wordent *);
4345e5710bSMark Peek static void hfree (struct Hist *);
44c80476e4SDavid E. O'Brien
45c80476e4SDavid E. O'Brien #define HIST_ONLY 0x01
46c80476e4SDavid E. O'Brien #define HIST_SAVE 0x02
47c80476e4SDavid E. O'Brien #define HIST_LOAD 0x04
48c80476e4SDavid E. O'Brien #define HIST_REV 0x08
49c80476e4SDavid E. O'Brien #define HIST_CLEAR 0x10
50c80476e4SDavid E. O'Brien #define HIST_MERGE 0x20
51c80476e4SDavid E. O'Brien #define HIST_TIME 0x40
52c80476e4SDavid E. O'Brien
53c80476e4SDavid E. O'Brien /*
54c80476e4SDavid E. O'Brien * C shell
55c80476e4SDavid E. O'Brien */
56c80476e4SDavid E. O'Brien
579ccc37e3SMark Peek /* Static functions don't show up in gprof summaries. So eliminate "static"
589ccc37e3SMark Peek * modifier from some frequently called functions. */
599ccc37e3SMark Peek #ifdef PROF
609ccc37e3SMark Peek #define PG_STATIC
619ccc37e3SMark Peek #else
629ccc37e3SMark Peek #define PG_STATIC static
639ccc37e3SMark Peek #endif
649ccc37e3SMark Peek
659ccc37e3SMark Peek /* #define DEBUG_HIST 1 */
669ccc37e3SMark Peek
679ccc37e3SMark Peek static const int fastMergeErase = 1;
689ccc37e3SMark Peek static unsigned histCount = 0; /* number elements on history list */
6919d2e3deSDmitry Chagin static int histlen = 0;
709ccc37e3SMark Peek static struct Hist *histTail = NULL; /* last element on history list */
719ccc37e3SMark Peek static struct Hist *histMerg = NULL; /* last element merged by Htime */
729ccc37e3SMark Peek
739ccc37e3SMark Peek static void insertHistHashTable(struct Hist *, unsigned);
749ccc37e3SMark Peek
759ccc37e3SMark Peek /* Insert new element (hp) in history list after specified predecessor (pp). */
769ccc37e3SMark Peek static void
hinsert(struct Hist * hp,struct Hist * pp)779ccc37e3SMark Peek hinsert(struct Hist *hp, struct Hist *pp)
789ccc37e3SMark Peek {
799ccc37e3SMark Peek struct Hist *fp = pp->Hnext; /* following element, if any */
809ccc37e3SMark Peek hp->Hnext = fp, hp->Hprev = pp;
819ccc37e3SMark Peek pp->Hnext = hp;
829ccc37e3SMark Peek if (fp)
839ccc37e3SMark Peek fp->Hprev = hp;
849ccc37e3SMark Peek else
859ccc37e3SMark Peek histTail = hp; /* meaning hp->Hnext == NULL */
869ccc37e3SMark Peek histCount++;
879ccc37e3SMark Peek }
889ccc37e3SMark Peek
899ccc37e3SMark Peek /* Remove the entry from the history list. */
909ccc37e3SMark Peek static void
hremove(struct Hist * hp)919ccc37e3SMark Peek hremove(struct Hist *hp)
929ccc37e3SMark Peek {
939ccc37e3SMark Peek struct Hist *pp = hp->Hprev;
949ccc37e3SMark Peek assert(pp); /* elements always have a previous */
959ccc37e3SMark Peek pp->Hnext = hp->Hnext;
969ccc37e3SMark Peek if (hp->Hnext)
979ccc37e3SMark Peek hp->Hnext->Hprev = pp;
989ccc37e3SMark Peek else
999ccc37e3SMark Peek histTail = pp; /* we must have been last */
1009ccc37e3SMark Peek if (hp == histMerg) /* deleting this hint from list */
1019ccc37e3SMark Peek histMerg = NULL;
1029ccc37e3SMark Peek assert(histCount > 0);
1039ccc37e3SMark Peek histCount--;
1049ccc37e3SMark Peek }
1059ccc37e3SMark Peek
1069ccc37e3SMark Peek /* Prune length of history list to specified size by history variable. */
1079ccc37e3SMark Peek PG_STATIC void
discardExcess(int hlen)10819d2e3deSDmitry Chagin discardExcess(int hlen)
109c80476e4SDavid E. O'Brien {
11023338178SMark Peek struct Hist *hp, *np;
1119ccc37e3SMark Peek if (histTail == NULL) {
1129ccc37e3SMark Peek assert(histCount == 0);
1139ccc37e3SMark Peek return; /* no entries on history list */
1149ccc37e3SMark Peek }
1159ccc37e3SMark Peek /* Prune dummy entries from the front, then old entries from the back. If
1169ccc37e3SMark Peek * the list is still too long scan the whole list as before. But only do a
1179ccc37e3SMark Peek * full scan if the list is more than 6% (1/16th) too long. */
11819d2e3deSDmitry Chagin while (histCount > (unsigned)hlen && (np = Histlist.Hnext)) {
11919d2e3deSDmitry Chagin if (eventno - np->Href >= hlen || hlen == 0)
1209ccc37e3SMark Peek hremove(np), hfree(np);
1219ccc37e3SMark Peek else
1229ccc37e3SMark Peek break;
1239ccc37e3SMark Peek }
12419d2e3deSDmitry Chagin while (histCount > (unsigned)hlen && (np = histTail) != &Histlist) {
12519d2e3deSDmitry Chagin if (eventno - np->Href >= hlen || hlen == 0)
1269ccc37e3SMark Peek hremove(np), hfree(np);
1279ccc37e3SMark Peek else
1289ccc37e3SMark Peek break;
1299ccc37e3SMark Peek }
13019d2e3deSDmitry Chagin if (histCount - (hlen >> 4) <= (unsigned)hlen)
1319ccc37e3SMark Peek return; /* don't bother doing the full scan */
13219d2e3deSDmitry Chagin for (hp = &Histlist; histCount > (unsigned)hlen &&
1339ccc37e3SMark Peek (np = hp->Hnext) != NULL;)
13419d2e3deSDmitry Chagin if (eventno - np->Href >= hlen || hlen == 0)
1359ccc37e3SMark Peek hremove(np), hfree(np);
1369ccc37e3SMark Peek else
1379ccc37e3SMark Peek hp = np;
1389ccc37e3SMark Peek }
1399ccc37e3SMark Peek
1409ccc37e3SMark Peek /* Add the command "sp" to the history list. */
1419ccc37e3SMark Peek void
savehist(struct wordent * sp,int mflg)1429ccc37e3SMark Peek savehist(
1439ccc37e3SMark Peek struct wordent *sp,
1449ccc37e3SMark Peek int mflg) /* true if -m (merge) specified */
1459ccc37e3SMark Peek {
146c80476e4SDavid E. O'Brien /* throw away null lines */
147c80476e4SDavid E. O'Brien if (sp && sp->next->word[0] == '\n')
148c80476e4SDavid E. O'Brien return;
149c80476e4SDavid E. O'Brien if (sp)
1509ccc37e3SMark Peek (void) enthist(++eventno, sp, 1, mflg, histlen);
1519ccc37e3SMark Peek discardExcess(histlen);
152c80476e4SDavid E. O'Brien }
153c80476e4SDavid E. O'Brien
1549ccc37e3SMark Peek #define USE_JENKINS_HASH 1
1559ccc37e3SMark Peek /* #define USE_ONE_AT_A_TIME 1 */
1569ccc37e3SMark Peek #undef PRIME_LENGTH /* no need for good HTL */
1579ccc37e3SMark Peek
1589ccc37e3SMark Peek #ifdef USE_JENKINS_HASH
1599ccc37e3SMark Peek #define hashFcnName "lookup3"
1609ccc37e3SMark Peek /* From:
1619ccc37e3SMark Peek lookup3.c, by Bob Jenkins, May 2006, Public Domain.
1629ccc37e3SMark Peek "... You can use this free for any purpose. It's in
1639ccc37e3SMark Peek the public domain. It has no warranty."
1649ccc37e3SMark Peek http://burtleburtle.net/bob/hash/index.html
1659ccc37e3SMark Peek */
1669ccc37e3SMark Peek
1679ccc37e3SMark Peek #define rot(x,k) (((x)<<(k)) | ((x)>>(32-(k))))
1689ccc37e3SMark Peek #define mix(a,b,c) \
1699ccc37e3SMark Peek { \
1709ccc37e3SMark Peek a -= c; a ^= rot(c, 4); c += b; \
1719ccc37e3SMark Peek b -= a; b ^= rot(a, 6); a += c; \
1729ccc37e3SMark Peek c -= b; c ^= rot(b, 8); b += a; \
1739ccc37e3SMark Peek a -= c; a ^= rot(c,16); c += b; \
1749ccc37e3SMark Peek b -= a; b ^= rot(a,19); a += c; \
1759ccc37e3SMark Peek c -= b; c ^= rot(b, 4); b += a; \
1769ccc37e3SMark Peek }
1779ccc37e3SMark Peek #define final(a,b,c) \
1789ccc37e3SMark Peek { \
1799ccc37e3SMark Peek c ^= b; c -= rot(b,14); \
1809ccc37e3SMark Peek a ^= c; a -= rot(c,11); \
1819ccc37e3SMark Peek b ^= a; b -= rot(a,25); \
1829ccc37e3SMark Peek c ^= b; c -= rot(b,16); \
1839ccc37e3SMark Peek a ^= c; a -= rot(c, 4); \
1849ccc37e3SMark Peek b ^= a; b -= rot(a,14); \
1859ccc37e3SMark Peek c ^= b; c -= rot(b,24); \
1869ccc37e3SMark Peek }
1879ccc37e3SMark Peek
1889ccc37e3SMark Peek struct hashValue /* State used to hash a wordend word list. */
1899ccc37e3SMark Peek {
1909ccc37e3SMark Peek uint32_t a, b, c;
1919ccc37e3SMark Peek };
1929ccc37e3SMark Peek
1939ccc37e3SMark Peek /* Set up the internal state */
1949ccc37e3SMark Peek static void
initializeHash(struct hashValue * h)1959ccc37e3SMark Peek initializeHash(struct hashValue *h)
1969ccc37e3SMark Peek {
1979ccc37e3SMark Peek h->a = h->b = h->c = 0xdeadbeef;
1989ccc37e3SMark Peek }
1999ccc37e3SMark Peek
2009ccc37e3SMark Peek /* This does a partial hash of the Chars in a single word. For efficiency we
2019ccc37e3SMark Peek * include 3 versions of the code to pack Chars into 32-bit words for the
2029ccc37e3SMark Peek * mixing function. */
2039ccc37e3SMark Peek static void
addWordToHash(struct hashValue * h,const Char * word)2049ccc37e3SMark Peek addWordToHash(struct hashValue *h, const Char *word)
2059ccc37e3SMark Peek {
2069ccc37e3SMark Peek uint32_t a = h->a, b = h->b, c = h->c;
2079ccc37e3SMark Peek #ifdef SHORT_STRINGS
208*6560ac57SDmitry Chagin #define GETK if ((k = (uChar)*word++) == 0) break
2099ccc37e3SMark Peek #ifdef WIDE_STRINGS
2109ccc37e3SMark Peek assert(sizeof(Char) >= 4);
2119ccc37e3SMark Peek while (1) {
2129ccc37e3SMark Peek unsigned k;
213*6560ac57SDmitry Chagin GETK;
214*6560ac57SDmitry Chagin a += k;
215*6560ac57SDmitry Chagin GETK;
216*6560ac57SDmitry Chagin b += k;
217*6560ac57SDmitry Chagin GETK;
218*6560ac57SDmitry Chagin c += k;
2199ccc37e3SMark Peek mix(a, b, c);
2209ccc37e3SMark Peek }
2219ccc37e3SMark Peek #else
2229ccc37e3SMark Peek assert(sizeof(Char) == 2);
2239ccc37e3SMark Peek while (1) {
2249ccc37e3SMark Peek unsigned k;
225*6560ac57SDmitry Chagin GETK;
226*6560ac57SDmitry Chagin a += k;
227*6560ac57SDmitry Chagin GETK;
228*6560ac57SDmitry Chagin a += k << 16;
229*6560ac57SDmitry Chagin GETK;
230*6560ac57SDmitry Chagin b += k;
231*6560ac57SDmitry Chagin GETK;
232*6560ac57SDmitry Chagin b += k << 16;
233*6560ac57SDmitry Chagin GETK;
234*6560ac57SDmitry Chagin c += k;
235*6560ac57SDmitry Chagin GETK;
236*6560ac57SDmitry Chagin c += k << 16;
2379ccc37e3SMark Peek mix(a, b, c);
2389ccc37e3SMark Peek }
2399ccc37e3SMark Peek #endif
2409ccc37e3SMark Peek #else
2419ccc37e3SMark Peek assert(sizeof(Char) == 1);
2429ccc37e3SMark Peek while (1) {
2439ccc37e3SMark Peek unsigned k;
2449ccc37e3SMark Peek if ((k = *word++) == 0) break; a += k;
2459ccc37e3SMark Peek if ((k = *word++) == 0) break; a += k << 8;
2469ccc37e3SMark Peek if ((k = *word++) == 0) break; a += k << 16;
2479ccc37e3SMark Peek if ((k = *word++) == 0) break; a += k << 24;
2489ccc37e3SMark Peek if ((k = *word++) == 0) break; b += k;
2499ccc37e3SMark Peek if ((k = *word++) == 0) break; b += k << 8;
2509ccc37e3SMark Peek if ((k = *word++) == 0) break; b += k << 16;
2519ccc37e3SMark Peek if ((k = *word++) == 0) break; b += k << 24;
2529ccc37e3SMark Peek if ((k = *word++) == 0) break; c += k;
2539ccc37e3SMark Peek if ((k = *word++) == 0) break; c += k << 8;
2549ccc37e3SMark Peek if ((k = *word++) == 0) break; c += k << 16;
2559ccc37e3SMark Peek if ((k = *word++) == 0) break; c += k << 24;
2569ccc37e3SMark Peek mix(a, b, c);
2579ccc37e3SMark Peek }
2589ccc37e3SMark Peek #endif
2599ccc37e3SMark Peek h->a = a, h->b = b, h->c = c;
2609ccc37e3SMark Peek }
2619ccc37e3SMark Peek
2629ccc37e3SMark Peek static void
addCharToHash(struct hashValue * h,Char ch)2639ccc37e3SMark Peek addCharToHash(struct hashValue *h, Char ch)
2649ccc37e3SMark Peek {
2659ccc37e3SMark Peek /* The compiler (gcc -O2) seems to do a good job optimizing this without
2669ccc37e3SMark Peek * explicitly extracting into local variables. */
2679ccc37e3SMark Peek h->a += (uChar)ch;
2689ccc37e3SMark Peek mix(h->a, h->b, h->c);
2699ccc37e3SMark Peek }
2709ccc37e3SMark Peek
2719ccc37e3SMark Peek static uint32_t
finalizeHash(struct hashValue * h)2729ccc37e3SMark Peek finalizeHash(struct hashValue *h)
2739ccc37e3SMark Peek {
2749ccc37e3SMark Peek uint32_t a = h->a, b = h->b, c = h->c;
2759ccc37e3SMark Peek final(a, b, c);
2769ccc37e3SMark Peek return c;
2779ccc37e3SMark Peek }
2789ccc37e3SMark Peek
2799ccc37e3SMark Peek #elif USE_ONE_AT_A_TIME
2809ccc37e3SMark Peek #define hashFcnName "one-at-a-time"
2819ccc37e3SMark Peek /* This one is also from Bob Jenkins, but is slower but simpler than lookup3.
2829ccc37e3SMark Peek "... The code given here are all public domain."
2839ccc37e3SMark Peek http://burtleburtle.net/bob/hash/doobs.html */
2849ccc37e3SMark Peek
2859ccc37e3SMark Peek #if 0
2869ccc37e3SMark Peek ub4
2879ccc37e3SMark Peek one_at_a_time(char *key, ub4 len)
2889ccc37e3SMark Peek {
2899ccc37e3SMark Peek ub4 hash, i;
2909ccc37e3SMark Peek for (hash=0, i=0; i<len; ++i)
2919ccc37e3SMark Peek {
2929ccc37e3SMark Peek hash += key[i];
2939ccc37e3SMark Peek hash += (hash << 10);
2949ccc37e3SMark Peek hash ^= (hash >> 6);
2959ccc37e3SMark Peek }
2969ccc37e3SMark Peek hash += (hash << 3);
2979ccc37e3SMark Peek hash ^= (hash >> 11);
2989ccc37e3SMark Peek hash += (hash << 15);
2999ccc37e3SMark Peek return (hash & mask);
3009ccc37e3SMark Peek }
3019ccc37e3SMark Peek #endif
3029ccc37e3SMark Peek
3039ccc37e3SMark Peek struct hashValue { uint32_t h; };
3049ccc37e3SMark Peek static void
initializeHash(struct hashValue * h)3059ccc37e3SMark Peek initializeHash(struct hashValue *h)
3069ccc37e3SMark Peek {
3079ccc37e3SMark Peek h->h = 0;
3089ccc37e3SMark Peek }
3099ccc37e3SMark Peek
3109ccc37e3SMark Peek static void
addWordToHash(struct hashValue * h,const Char * word)3119ccc37e3SMark Peek addWordToHash(struct hashValue *h, const Char *word)
3129ccc37e3SMark Peek {
3139ccc37e3SMark Peek unsigned k;
3149ccc37e3SMark Peek uint32_t hash = h->h;
3159ccc37e3SMark Peek while (k = (uChar)*word++)
3169ccc37e3SMark Peek hash += k, hash += hash << 10, hash ^= hash >> 6;
3179ccc37e3SMark Peek h->h = hash;
3189ccc37e3SMark Peek }
3199ccc37e3SMark Peek
3209ccc37e3SMark Peek static void
addCharToHash(struct hashValue * h,Char c)3219ccc37e3SMark Peek addCharToHash(struct hashValue *h, Char c)
3229ccc37e3SMark Peek {
3239ccc37e3SMark Peek Char b[2] = { c, 0 };
3249ccc37e3SMark Peek addWordToHash(h, b);
3259ccc37e3SMark Peek }
3269ccc37e3SMark Peek
3279ccc37e3SMark Peek static uint32_t
finalizeHash(struct hashValue * h)3289ccc37e3SMark Peek finalizeHash(struct hashValue *h)
3299ccc37e3SMark Peek {
3309ccc37e3SMark Peek unsigned hash = h->h;
3319ccc37e3SMark Peek hash += (hash << 3);
3329ccc37e3SMark Peek hash ^= (hash >> 11);
3339ccc37e3SMark Peek hash += (hash << 15);
3349ccc37e3SMark Peek return hash;
3359ccc37e3SMark Peek }
3369ccc37e3SMark Peek
3379ccc37e3SMark Peek #else
3389ccc37e3SMark Peek #define hashFcnName "add-mul"
3399ccc37e3SMark Peek /* Simple multipy and add hash. */
3409ccc37e3SMark Peek #define PRIME_LENGTH 1 /* need "good" HTL */
3419ccc37e3SMark Peek struct hashValue { uint32_t h; };
3429ccc37e3SMark Peek static void
initializeHash(struct hashValue * h)3439ccc37e3SMark Peek initializeHash(struct hashValue *h)
3449ccc37e3SMark Peek {
3459ccc37e3SMark Peek h->h = 0xe13e2345;
3469ccc37e3SMark Peek }
3479ccc37e3SMark Peek
3489ccc37e3SMark Peek static void
addWordToHash(struct hashValue * h,const Char * word)3499ccc37e3SMark Peek addWordToHash(struct hashValue *h, const Char *word)
3509ccc37e3SMark Peek {
3519ccc37e3SMark Peek unsigned k;
3529ccc37e3SMark Peek uint32_t hash = h->h;
3539ccc37e3SMark Peek while (k = (uChar)*word++)
3549ccc37e3SMark Peek hash = hash * 0x9e4167b9 + k;
3559ccc37e3SMark Peek h->h = hash;
3569ccc37e3SMark Peek }
3579ccc37e3SMark Peek
3589ccc37e3SMark Peek static void
addCharToHash(struct hashValue * h,Char c)3599ccc37e3SMark Peek addCharToHash(struct hashValue *h, Char c)
3609ccc37e3SMark Peek {
3619ccc37e3SMark Peek h->h = h->h * 0x9e4167b9 + (uChar)c;
3629ccc37e3SMark Peek }
3639ccc37e3SMark Peek
3649ccc37e3SMark Peek static uint32_t
finalizeHash(struct hashValue * h)3659ccc37e3SMark Peek finalizeHash(struct hashValue *h)
3669ccc37e3SMark Peek {
3679ccc37e3SMark Peek return h->h;
3689ccc37e3SMark Peek }
3699ccc37e3SMark Peek #endif
3709ccc37e3SMark Peek
3719ccc37e3SMark Peek static unsigned
hashhist(struct wordent * h0)3729ccc37e3SMark Peek hashhist(struct wordent *h0)
3739ccc37e3SMark Peek {
3749ccc37e3SMark Peek struct hashValue s;
3759ccc37e3SMark Peek struct wordent *firstWord = h0->next;
3769ccc37e3SMark Peek struct wordent *h = firstWord;
3779ccc37e3SMark Peek unsigned hash = 0;
3789ccc37e3SMark Peek
3799ccc37e3SMark Peek initializeHash(&s);
3809ccc37e3SMark Peek for (; h != h0; h = h->next) {
3819ccc37e3SMark Peek if (h->word[0] == '\n')
3829ccc37e3SMark Peek break; /* don't hash newline */
3839ccc37e3SMark Peek if (h != firstWord)
3849ccc37e3SMark Peek addCharToHash(&s, ' '); /* space between words */
3859ccc37e3SMark Peek addWordToHash(&s, h->word);
3869ccc37e3SMark Peek }
3879ccc37e3SMark Peek hash = finalizeHash(&s);
3889ccc37e3SMark Peek /* Zero means no hash value, so never return zero as a hash value. */
3899ccc37e3SMark Peek return hash ? hash : 0x7fffffff; /* prime! */
3909ccc37e3SMark Peek }
3919ccc37e3SMark Peek
3929ccc37e3SMark Peek #if 0
3939ccc37e3SMark Peek unsigned
3949ccc37e3SMark Peek hashStr(Char *str)
3959ccc37e3SMark Peek {
3969ccc37e3SMark Peek struct hashValue s;
3979ccc37e3SMark Peek initializeHash(&s);
3989ccc37e3SMark Peek addWordToHash(&s, str);
3999ccc37e3SMark Peek return finalizeHash(&s);
4009ccc37e3SMark Peek }
4019ccc37e3SMark Peek #endif
4029ccc37e3SMark Peek
4039ccc37e3SMark Peek #ifdef PRIME_LENGTH /* need good HTL */
4049ccc37e3SMark Peek #define hash2tableIndex(hash, len) ((hash) % len)
4059ccc37e3SMark Peek #else
4069ccc37e3SMark Peek #define hash2tableIndex(hash, len) ((hash) & (len-1))
4079ccc37e3SMark Peek #endif
4089ccc37e3SMark Peek
4099ccc37e3SMark Peek /* This code can be enabled to test the above hash functions for speed and
4109ccc37e3SMark Peek * collision avoidance. The testing is enabled by "occasional" calls to
4119ccc37e3SMark Peek * displayHistStats(), see which. */
4129ccc37e3SMark Peek #ifdef DEBUG_HIST
4139ccc37e3SMark Peek
4149ccc37e3SMark Peek #ifdef BSDTIMES
4159ccc37e3SMark Peek static double
doTiming(int start)4169ccc37e3SMark Peek doTiming(int start) {
4179ccc37e3SMark Peek static struct timeval beginTime;
4189ccc37e3SMark Peek if (start) {
4199ccc37e3SMark Peek gettimeofday(&beginTime, NULL);
4209ccc37e3SMark Peek return 0.0;
4219ccc37e3SMark Peek } else {
4229ccc37e3SMark Peek struct timeval now;
4239ccc37e3SMark Peek gettimeofday(&now, NULL);
4249ccc37e3SMark Peek return (now.tv_sec-beginTime.tv_sec) +
4259ccc37e3SMark Peek (now.tv_usec-beginTime.tv_usec)/1e6;
4269ccc37e3SMark Peek }
4279ccc37e3SMark Peek }
4289ccc37e3SMark Peek #else
4299ccc37e3SMark Peek static double
doTiming(int start)4309ccc37e3SMark Peek doTiming(int start) {
4319ccc37e3SMark Peek USE(start);
4329ccc37e3SMark Peek return 0.0;
4339ccc37e3SMark Peek }
4349ccc37e3SMark Peek #endif
4359ccc37e3SMark Peek
4369ccc37e3SMark Peek static void
generateHashes(int nChars,unsigned nWords,unsigned samples,unsigned * hashes,unsigned length)4379ccc37e3SMark Peek generateHashes(int nChars, unsigned nWords, unsigned samples, unsigned *hashes,
4389ccc37e3SMark Peek unsigned length)
4399ccc37e3SMark Peek {
4409ccc37e3SMark Peek if (nChars < 1)
4419ccc37e3SMark Peek return;
4429ccc37e3SMark Peek nWords = (nWords < 1) ? 1 : (nWords > 4) ? 4 : nWords;
4439ccc37e3SMark Peek Char *number = xmalloc((nChars+nWords)*sizeof(Char));
4449ccc37e3SMark Peek struct wordent word[4];
4459ccc37e3SMark Peek struct wordent base = { NULL, &word[0], &word[0] };
4469ccc37e3SMark Peek word[0].word = number, word[0].next = &base, word[0].prev = &base;
4479ccc37e3SMark Peek unsigned w = 0; /* word number */
4489ccc37e3SMark Peek /* Generate multiple words of length 2, 3, 5, then all the rest. */
4499ccc37e3SMark Peek unsigned wBoundaries[4] = { 2-1, 2+3-1, 2+3+5-1, 0 };
4509ccc37e3SMark Peek /* Ensure the last word has at least 4 Chars in it. */
4519ccc37e3SMark Peek while (nWords >= 2 && nChars < (wBoundaries[nWords-2]+1) + 4)
4529ccc37e3SMark Peek nWords--;
4539ccc37e3SMark Peek wBoundaries[nWords-1] = 0xffffffff; /* don't end word past this point */
4549ccc37e3SMark Peek unsigned i;
4559ccc37e3SMark Peek for (i = 0; i<nChars; i++) {
4569ccc37e3SMark Peek /* In deference to the gawd awful add-mul hash, we won't use the worse
4579ccc37e3SMark Peek * case here (setting all Chars to 1), but assume mostly (or at least
4589ccc37e3SMark Peek * initially) ASCII data. */
4599ccc37e3SMark Peek number[i+w] = '!'; /* 0x21 = 33 */
4609ccc37e3SMark Peek
4619ccc37e3SMark Peek if (i == wBoundaries[w]) { /* end a word here and move to next */
4629ccc37e3SMark Peek w++; /* next word */
4639ccc37e3SMark Peek number[i+w] = 0; /* terminate */
4649ccc37e3SMark Peek word[w].word = &number[i+w+1];
4659ccc37e3SMark Peek word[w].next = &base, word[w].prev = &word[w-1];
4669ccc37e3SMark Peek word[w-1].next = &word[w], base.prev = &word[w];
4679ccc37e3SMark Peek }
4689ccc37e3SMark Peek }
4699ccc37e3SMark Peek /* w is the index of the last word actually created. */
4709ccc37e3SMark Peek number[nChars + w] = 0; /* terminate last word */
4719ccc37e3SMark Peek unsigned timeLimit = !samples;
4729ccc37e3SMark Peek if (samples == 0)
4739ccc37e3SMark Peek samples = 1000000000;
4749ccc37e3SMark Peek doTiming(1);
4759ccc37e3SMark Peek double sec;
4769ccc37e3SMark Peek for (i = 0; i < samples; i++) {
4779ccc37e3SMark Peek /* increment 4 digit base 255 number; last characters vary fastest */
4789ccc37e3SMark Peek unsigned j = nChars-1 + w;
4799ccc37e3SMark Peek while (1) {
4809ccc37e3SMark Peek if (++number[j] != 0)
4819ccc37e3SMark Peek break;
4829ccc37e3SMark Peek /* else reset this digit and proceed to next one */
4839ccc37e3SMark Peek number[j] = 1;
4849ccc37e3SMark Peek if (&number[j] <= word[w].word)
4859ccc37e3SMark Peek break; /* stop at beginning of last word */
4869ccc37e3SMark Peek j--;
4879ccc37e3SMark Peek }
4889ccc37e3SMark Peek if (word[w].word[0] == '\n')
4899ccc37e3SMark Peek word[w].word[0]++; /* suppress newline character */
4909ccc37e3SMark Peek unsigned hash = hashhist(&base);
4919ccc37e3SMark Peek hashes[hash2tableIndex(hash, length)]++;
4929ccc37e3SMark Peek if (timeLimit && (i & 0x3ffff) == 0x3ffff) {
4939ccc37e3SMark Peek sec = doTiming(0);
4949ccc37e3SMark Peek if (sec > 10)
4959ccc37e3SMark Peek break;
4969ccc37e3SMark Peek }
4979ccc37e3SMark Peek }
4989ccc37e3SMark Peek if (i >= samples)
4999ccc37e3SMark Peek sec = doTiming(0);
5009ccc37e3SMark Peek else
5019ccc37e3SMark Peek samples = i; /* number we actually did */
5029ccc37e3SMark Peek if (sec > 0.01) {
5039ccc37e3SMark Peek xprintf("Hash %d (%d Char %u words) with %s: %d nsec/hash, %d mcps\n",
5049ccc37e3SMark Peek samples, nChars, w+1, hashFcnName, (int)((sec/samples)*1e9),
5059ccc37e3SMark Peek (int)((double)samples*nChars/sec/1e6));
5069ccc37e3SMark Peek }
5079ccc37e3SMark Peek }
5089ccc37e3SMark Peek #endif /* DEBUG_HIST */
5099ccc37e3SMark Peek
5109ccc37e3SMark Peek #ifdef DEBUG_HIST
5119ccc37e3SMark Peek static void
testHash(void)5129ccc37e3SMark Peek testHash(void)
5139ccc37e3SMark Peek {
5149ccc37e3SMark Peek static const Char STRtestHashTimings[] =
5159ccc37e3SMark Peek { 't','e','s','t','H','a','s','h','T','i','m','i','n','g','s', 0 };
5169ccc37e3SMark Peek struct varent *vp = adrof(STRtestHashTimings);
5179ccc37e3SMark Peek if (vp && vp->vec) {
5189ccc37e3SMark Peek unsigned hashes[4]; /* dummy place to put hashes */
5199ccc37e3SMark Peek Char **vals = vp->vec;
5209ccc37e3SMark Peek while (*vals) {
5219ccc37e3SMark Peek int length = getn(*vals);
5229ccc37e3SMark Peek unsigned words =
5239ccc37e3SMark Peek (length < 5) ? 1 : (length < 25) ? 2 : (length < 75) ? 3 : 4;
5249ccc37e3SMark Peek if (length > 0)
5259ccc37e3SMark Peek generateHashes(length, words, 0, hashes, 4);
5269ccc37e3SMark Peek vals++;
5279ccc37e3SMark Peek }
5289ccc37e3SMark Peek }
5299ccc37e3SMark Peek unsigned length = 1024;
5309ccc37e3SMark Peek #ifdef PRIME_LENGTH /* need good HTL */
5319ccc37e3SMark Peek length = 1021;
5329ccc37e3SMark Peek #endif
5339ccc37e3SMark Peek unsigned *hashes = xmalloc(length*sizeof(unsigned));
5349ccc37e3SMark Peek memset(hashes, 0, length*sizeof(unsigned));
5359ccc37e3SMark Peek /* Compute collision statistics for half full hashes modulo "length". */
5369ccc37e3SMark Peek generateHashes(4, 1, length/2, hashes, length);
5379ccc37e3SMark Peek /* Evaluate collisions by comparing occupancy rates (mean value 0.5).
5389ccc37e3SMark Peek * One bin for each number of hits. */
5399ccc37e3SMark Peek unsigned bins[155];
5409ccc37e3SMark Peek memset(bins, 0, sizeof(bins));
5419ccc37e3SMark Peek unsigned highest = 0;
5429ccc37e3SMark Peek unsigned i;
5439ccc37e3SMark Peek for (i = 0; i<length; i++) {
5449ccc37e3SMark Peek unsigned hits = hashes[i];
5459ccc37e3SMark Peek if (hits >= sizeof(bins)/sizeof(bins[0])) /* clip */
5469ccc37e3SMark Peek hits = highest = sizeof(bins)/sizeof(bins[0]) - 1;
5479ccc37e3SMark Peek if (hits > highest)
5489ccc37e3SMark Peek highest = hits;
5499ccc37e3SMark Peek bins[hits]++;
5509ccc37e3SMark Peek }
5519ccc37e3SMark Peek xprintf("Occupancy of %d buckets by %d hashes %d Chars %d word with %s\n",
5529ccc37e3SMark Peek length, length/2, 4, 1, hashFcnName);
5539ccc37e3SMark Peek for (i = 0; i <= highest; i++) {
5549ccc37e3SMark Peek xprintf(" %d buckets (%d%%) with %d hits\n",
5559ccc37e3SMark Peek bins[i], bins[i]*100/length, i);
5569ccc37e3SMark Peek }
5579ccc37e3SMark Peek /* Count run lengths to evaluate linear rehashing effectiveness. Estimate
5589ccc37e3SMark Peek * a little corrupted by edge effects. */
5599ccc37e3SMark Peek memset(bins, 0, sizeof(bins));
5609ccc37e3SMark Peek highest = 0;
5619ccc37e3SMark Peek for (i = 0; hashes[i] == 0; i++); /* find first occupied bucket */
5629ccc37e3SMark Peek unsigned run = 0;
5639ccc37e3SMark Peek unsigned rehashed = 0;
5649ccc37e3SMark Peek for (; i<length; i++) {
5659ccc37e3SMark Peek unsigned hits = hashes[i];
5669ccc37e3SMark Peek if (hits == 0 && rehashed > 0)
5679ccc37e3SMark Peek hits = 1 && rehashed--;
5689ccc37e3SMark Peek else if (hits > 1)
5699ccc37e3SMark Peek rehashed += hits-1;
5709ccc37e3SMark Peek if (hits)
5719ccc37e3SMark Peek run++;
5729ccc37e3SMark Peek else {
5739ccc37e3SMark Peek /* a real free slot, count it */
5749ccc37e3SMark Peek if (run >= sizeof(bins)/sizeof(bins[0])) /* clip */
5759ccc37e3SMark Peek run = highest = sizeof(bins)/sizeof(bins[0]) - 1;
5769ccc37e3SMark Peek if (run > highest)
5779ccc37e3SMark Peek highest = run;
5789ccc37e3SMark Peek bins[run]++;
5799ccc37e3SMark Peek run = 0;
5809ccc37e3SMark Peek }
5819ccc37e3SMark Peek }
5829ccc37e3SMark Peek /* Ignore the partial run at end as we ignored the beginning. */
5839ccc37e3SMark Peek double merit = 0.0, entries = 0;
5849ccc37e3SMark Peek for (i = 0; i <= highest; i++) {
5859ccc37e3SMark Peek entries += bins[i]*i; /* total hashed objects */
5869ccc37e3SMark Peek merit += bins[i]*i*i;
5879ccc37e3SMark Peek }
5889ccc37e3SMark Peek xprintf("Rehash collision figure of merit %u (ideal=100), run lengths:\n",
5899ccc37e3SMark Peek (int)(100.0*merit/entries));
5909ccc37e3SMark Peek for (i = 0; i <= highest; i++) {
5919ccc37e3SMark Peek if (bins[i] != 0)
5929ccc37e3SMark Peek xprintf(" %d runs of length %d buckets\n", bins[i], i);
5939ccc37e3SMark Peek }
5949ccc37e3SMark Peek xfree(hashes);
5959ccc37e3SMark Peek }
5969ccc37e3SMark Peek #endif /* DEBUG_HIST */
5979ccc37e3SMark Peek
5989ccc37e3SMark Peek /* Compares two word lists for equality. */
59923338178SMark Peek static int
heq(const struct wordent * a0,const struct wordent * b0)60045e5710bSMark Peek heq(const struct wordent *a0, const struct wordent *b0)
601c80476e4SDavid E. O'Brien {
60245e5710bSMark Peek const struct wordent *a = a0->next, *b = b0->next;
603c80476e4SDavid E. O'Brien
604c80476e4SDavid E. O'Brien for (;;) {
605c80476e4SDavid E. O'Brien if (Strcmp(a->word, b->word) != 0)
606c80476e4SDavid E. O'Brien return 0;
607c80476e4SDavid E. O'Brien a = a->next;
608c80476e4SDavid E. O'Brien b = b->next;
609c80476e4SDavid E. O'Brien if (a == a0)
610c80476e4SDavid E. O'Brien return (b == b0) ? 1 : 0;
611c80476e4SDavid E. O'Brien if (b == b0)
612c80476e4SDavid E. O'Brien return 0;
613c80476e4SDavid E. O'Brien }
614c80476e4SDavid E. O'Brien }
615c80476e4SDavid E. O'Brien
6169ccc37e3SMark Peek /* Renumber entries following p, which we will be deleting. */
6179ccc37e3SMark Peek PG_STATIC void
renumberHist(struct Hist * p)6189ccc37e3SMark Peek renumberHist(struct Hist *p)
619c80476e4SDavid E. O'Brien {
6209ccc37e3SMark Peek int n = p->Href;
6219ccc37e3SMark Peek while ((p = p->Hnext))
6229ccc37e3SMark Peek p->Href = n--;
6239ccc37e3SMark Peek }
6249ccc37e3SMark Peek
6259ccc37e3SMark Peek /* The hash table is implemented as an array of pointers to Hist entries. Each
6269ccc37e3SMark Peek * entry is located in the table using hash2tableIndex() and checking the
6279ccc37e3SMark Peek * following entries in case of a collision (linear rehash). Free entries in
6289ccc37e3SMark Peek * the table are zero (0, NULL, emptyHTE). Deleted entries that cannot yet be
6299ccc37e3SMark Peek * freed are set to one (deletedHTE). The Hist.Hhash member is non-zero iff
6309ccc37e3SMark Peek * the entry is in the hash table. When the hash table get too full, it is
6319ccc37e3SMark Peek * reallocated to be approximately twice the history length (see
6329ccc37e3SMark Peek * getHashTableSize). */
6339ccc37e3SMark Peek static struct Hist **histHashTable = NULL;
6349ccc37e3SMark Peek static unsigned histHashTableLength = 0; /* number of Hist pointers in table */
6359ccc37e3SMark Peek
6369ccc37e3SMark Peek static struct Hist * const emptyHTE = NULL;
6379ccc37e3SMark Peek static struct Hist * const deletedHTE = (struct Hist *)1;
6389ccc37e3SMark Peek
6399ccc37e3SMark Peek static struct {
6409ccc37e3SMark Peek unsigned insertCount;
6419ccc37e3SMark Peek unsigned removeCount;
6429ccc37e3SMark Peek unsigned rehashes;
6439ccc37e3SMark Peek int deleted;
6449ccc37e3SMark Peek } hashStats;
6459ccc37e3SMark Peek
6469ccc37e3SMark Peek #ifdef DEBUG_HIST
6479ccc37e3SMark Peek void
checkHistHashTable(int print)6489ccc37e3SMark Peek checkHistHashTable(int print)
6499ccc37e3SMark Peek {
6509ccc37e3SMark Peek unsigned occupied = 0;
6519ccc37e3SMark Peek unsigned deleted = 0;
6529ccc37e3SMark Peek unsigned i;
6539ccc37e3SMark Peek for (i = 0; i<histHashTableLength; i++)
6549ccc37e3SMark Peek if (histHashTable[i] == emptyHTE)
6559ccc37e3SMark Peek continue;
6569ccc37e3SMark Peek else if (histHashTable[i] == deletedHTE)
6579ccc37e3SMark Peek deleted++;
6589ccc37e3SMark Peek else
6599ccc37e3SMark Peek occupied++;
6609ccc37e3SMark Peek if (print)
6619ccc37e3SMark Peek xprintf(" found len %u occupied %u deleted %u\n",
6629ccc37e3SMark Peek histHashTableLength, occupied, deleted);
6639ccc37e3SMark Peek assert(deleted == hashStats.deleted);
6649ccc37e3SMark Peek }
6659ccc37e3SMark Peek
6669ccc37e3SMark Peek static int doneTest = 0;
6679ccc37e3SMark Peek
6689ccc37e3SMark Peek /* Main entry point for displaying history statistics and hash function
6699ccc37e3SMark Peek * behavior. */
6709ccc37e3SMark Peek void
displayHistStats(const char * reason)6719ccc37e3SMark Peek displayHistStats(const char *reason)
6729ccc37e3SMark Peek {
6739ccc37e3SMark Peek /* Just hash statistics for now. */
6749ccc37e3SMark Peek xprintf("%s history hash table len %u count %u (deleted %d)\n", reason,
6759ccc37e3SMark Peek histHashTableLength, histCount, hashStats.deleted);
6769ccc37e3SMark Peek xprintf(" inserts %u rehashes %u%% each\n",
6779ccc37e3SMark Peek hashStats.insertCount,
6789ccc37e3SMark Peek (hashStats.insertCount
6799ccc37e3SMark Peek ? 100*hashStats.rehashes/hashStats.insertCount : 0));
6809ccc37e3SMark Peek xprintf(" removes %u net %u\n",
6819ccc37e3SMark Peek hashStats.removeCount,
6829ccc37e3SMark Peek hashStats.insertCount - hashStats.removeCount);
6839ccc37e3SMark Peek assert(hashStats.insertCount >= hashStats.removeCount);
6849ccc37e3SMark Peek checkHistHashTable(1);
6859ccc37e3SMark Peek memset(&hashStats, 0, sizeof(hashStats));
6869ccc37e3SMark Peek if (!doneTest) {
6879ccc37e3SMark Peek testHash();
6889ccc37e3SMark Peek doneTest = 1;
6899ccc37e3SMark Peek }
6909ccc37e3SMark Peek }
6919ccc37e3SMark Peek #else
6929ccc37e3SMark Peek void
displayHistStats(const char * reason)6939ccc37e3SMark Peek displayHistStats(const char *reason)
6949ccc37e3SMark Peek {
6959ccc37e3SMark Peek USE(reason);
6969ccc37e3SMark Peek }
6979ccc37e3SMark Peek #endif
6989ccc37e3SMark Peek
6999ccc37e3SMark Peek static void
discardHistHashTable(void)7009ccc37e3SMark Peek discardHistHashTable(void)
7019ccc37e3SMark Peek {
7029ccc37e3SMark Peek if (histHashTable == NULL)
7039ccc37e3SMark Peek return;
7049ccc37e3SMark Peek displayHistStats("Discarding");
7059ccc37e3SMark Peek xfree(histHashTable);
7069ccc37e3SMark Peek histHashTable = NULL;
7079ccc37e3SMark Peek }
7089ccc37e3SMark Peek
7099ccc37e3SMark Peek /* Computes a new hash table size, when the current one is too small. */
7109ccc37e3SMark Peek static unsigned
getHashTableSize(int hlen)71119d2e3deSDmitry Chagin getHashTableSize(int hlen)
7129ccc37e3SMark Peek {
71319d2e3deSDmitry Chagin unsigned target = hlen * 2;
7149ccc37e3SMark Peek unsigned e = 5;
7159ccc37e3SMark Peek unsigned size;
7169ccc37e3SMark Peek while ((size = 1<<e) < target)
7179ccc37e3SMark Peek e++;
7189ccc37e3SMark Peek #ifdef PRIME_LENGTH /* need good HTL */
7199ccc37e3SMark Peek /* Not all prime, but most are and none have factors smaller than 11. */
7209ccc37e3SMark Peek return size+15;
7219ccc37e3SMark Peek #else
7229ccc37e3SMark Peek assert((size & (size-1)) == 0); /* must be a power of two */
7239ccc37e3SMark Peek return size;
7249ccc37e3SMark Peek #endif
7259ccc37e3SMark Peek }
7269ccc37e3SMark Peek
7279ccc37e3SMark Peek /* Create the hash table or resize, if necessary. */
7289ccc37e3SMark Peek static void
createHistHashTable(int hlen)72919d2e3deSDmitry Chagin createHistHashTable(int hlen)
7309ccc37e3SMark Peek {
73119d2e3deSDmitry Chagin if (hlen == 0) {
7329ccc37e3SMark Peek discardHistHashTable();
7339ccc37e3SMark Peek return;
7349ccc37e3SMark Peek }
73519d2e3deSDmitry Chagin if (hlen < 0) {
73619d2e3deSDmitry Chagin if (histlen <= 0)
7379ccc37e3SMark Peek return; /* no need for hash table */
73819d2e3deSDmitry Chagin hlen = histlen;
7399ccc37e3SMark Peek }
7409ccc37e3SMark Peek if (histHashTable != NULL) {
7419ccc37e3SMark Peek if (histCount < histHashTableLength * 3 / 4)
7429ccc37e3SMark Peek return; /* good enough for now */
7439ccc37e3SMark Peek discardHistHashTable(); /* too small */
7449ccc37e3SMark Peek }
7459ccc37e3SMark Peek histHashTableLength = getHashTableSize(
74619d2e3deSDmitry Chagin hlen > (int)histCount ? hlen : (int)histCount);
7479ccc37e3SMark Peek histHashTable = xmalloc(histHashTableLength * sizeof(struct Hist *));
7489ccc37e3SMark Peek memset(histHashTable, 0, histHashTableLength * sizeof(struct Hist *));
7499ccc37e3SMark Peek assert(histHashTable[0] == emptyHTE);
7509ccc37e3SMark Peek
7519ccc37e3SMark Peek /* Now insert all the entries on the history list into the hash table. */
7529ccc37e3SMark Peek {
7539ccc37e3SMark Peek struct Hist *hp;
7549ccc37e3SMark Peek for (hp = &Histlist; (hp = hp->Hnext) != NULL;) {
7559ccc37e3SMark Peek unsigned lpHash = hashhist(&hp->Hlex);
7569ccc37e3SMark Peek assert(!hp->Hhash || hp->Hhash == lpHash);
7579ccc37e3SMark Peek hp->Hhash = 0; /* force insert to new hash table */
7589ccc37e3SMark Peek insertHistHashTable(hp, lpHash);
7599ccc37e3SMark Peek }
7609ccc37e3SMark Peek }
7619ccc37e3SMark Peek }
7629ccc37e3SMark Peek
7639ccc37e3SMark Peek /* Insert np into the hash table. We assume that np is already on the
7649ccc37e3SMark Peek * Histlist. The specified hashval matches the new Hist entry but has not yet
7659ccc37e3SMark Peek * been assigned to Hhash (or the element is already on the hash table). */
7669ccc37e3SMark Peek static void
insertHistHashTable(struct Hist * np,unsigned hashval)7679ccc37e3SMark Peek insertHistHashTable(struct Hist *np, unsigned hashval)
7689ccc37e3SMark Peek {
7699ccc37e3SMark Peek unsigned rehashes = 0;
7709ccc37e3SMark Peek unsigned hi = 0;
7719ccc37e3SMark Peek if (!histHashTable)
7729ccc37e3SMark Peek return;
7739ccc37e3SMark Peek if (np->Hhash != 0) {
7749ccc37e3SMark Peek /* already in hash table */
7759ccc37e3SMark Peek assert(hashval == np->Hhash);
7769ccc37e3SMark Peek return;
7779ccc37e3SMark Peek }
7789ccc37e3SMark Peek assert(np != deletedHTE);
7799ccc37e3SMark Peek /* Find a free (empty or deleted) slot, using linear rehash. */
7809ccc37e3SMark Peek assert(histHashTable);
7819ccc37e3SMark Peek for (rehashes = 0;
7829ccc37e3SMark Peek ((hi = hash2tableIndex(hashval + rehashes, histHashTableLength)),
7839ccc37e3SMark Peek histHashTable[hi] != emptyHTE && histHashTable[hi] != deletedHTE);
7849ccc37e3SMark Peek rehashes++) {
7859ccc37e3SMark Peek assert(np != histHashTable[hi]);
7869ccc37e3SMark Peek if (rehashes >= histHashTableLength / 10) {
7879ccc37e3SMark Peek /* Hash table is full, so grow it. We assume the create function
7889ccc37e3SMark Peek * will roughly double the size we give it. Create initializes the
7899ccc37e3SMark Peek * new table with everything on the Histlist, so we are done when
7909ccc37e3SMark Peek * it returns. */
7919ccc37e3SMark Peek #ifdef DEBUG_HIST
7929ccc37e3SMark Peek xprintf("Growing history hash table from %d ...",
7939ccc37e3SMark Peek histHashTableLength);
7949ccc37e3SMark Peek flush();
7959ccc37e3SMark Peek #endif
7969ccc37e3SMark Peek discardHistHashTable();
7979ccc37e3SMark Peek createHistHashTable(histHashTableLength);
7989ccc37e3SMark Peek #ifdef DEBUG_HIST
7999ccc37e3SMark Peek xprintf("to %d.\n", histHashTableLength);
8009ccc37e3SMark Peek #endif
8019ccc37e3SMark Peek return;
8029ccc37e3SMark Peek }
8039ccc37e3SMark Peek }
8049ccc37e3SMark Peek /* Might be sensible to grow hash table if rehashes is "too big" here. */
8059ccc37e3SMark Peek if (histHashTable[hi] == deletedHTE)
8069ccc37e3SMark Peek hashStats.deleted--;
8079ccc37e3SMark Peek histHashTable[hi] = np;
8089ccc37e3SMark Peek np->Hhash = hashval;
8099ccc37e3SMark Peek hashStats.insertCount++;
8109ccc37e3SMark Peek hashStats.rehashes += rehashes;
8119ccc37e3SMark Peek }
8129ccc37e3SMark Peek
8139ccc37e3SMark Peek /* Remove the 'np' entry from the hash table. */
8149ccc37e3SMark Peek static void
removeHistHashTable(struct Hist * np)8159ccc37e3SMark Peek removeHistHashTable(struct Hist *np)
8169ccc37e3SMark Peek {
8179ccc37e3SMark Peek unsigned hi = np->Hhash;
8189ccc37e3SMark Peek if (!histHashTable || !hi)
8199ccc37e3SMark Peek return; /* no hash table or not on it */
8209ccc37e3SMark Peek /* find desired entry */
8219ccc37e3SMark Peek while ((hi = hash2tableIndex(hi, histHashTableLength)),
8229ccc37e3SMark Peek histHashTable[hi] != emptyHTE) {
8239ccc37e3SMark Peek if (np == histHashTable[hi]) {
8249ccc37e3SMark Peek unsigned i;
8259ccc37e3SMark Peek unsigned deletes = 0;
8269ccc37e3SMark Peek histHashTable[hi] = deletedHTE; /* dummy, but non-zero entry */
8279ccc37e3SMark Peek /* now peek ahead to see if the dummies are really necessary. */
8289ccc37e3SMark Peek i = 1;
8299ccc37e3SMark Peek while (histHashTable[hash2tableIndex(hi+i, histHashTableLength)] ==
8309ccc37e3SMark Peek deletedHTE)
8319ccc37e3SMark Peek i++;
8329ccc37e3SMark Peek if (histHashTable[hash2tableIndex(hi+i, histHashTableLength)] ==
8339ccc37e3SMark Peek emptyHTE) {
8349ccc37e3SMark Peek /* dummies are no longer necessary placeholders. */
8359ccc37e3SMark Peek deletes = i;
8369ccc37e3SMark Peek while (i-- > 0) {
8379ccc37e3SMark Peek histHashTable[hash2tableIndex(hi+i, histHashTableLength)] =
8389ccc37e3SMark Peek emptyHTE;
8399ccc37e3SMark Peek }
8409ccc37e3SMark Peek }
8419ccc37e3SMark Peek hashStats.deleted += 1 - deletes; /* delta deleted entries */
8429ccc37e3SMark Peek hashStats.removeCount++;
8439ccc37e3SMark Peek return;
8449ccc37e3SMark Peek }
8459ccc37e3SMark Peek hi++; /* linear rehash */
8469ccc37e3SMark Peek }
8479ccc37e3SMark Peek assert(!"Hist entry not found in hash table");
8489ccc37e3SMark Peek }
8499ccc37e3SMark Peek
8509ccc37e3SMark Peek /* Search the history hash table for a command matching lp, using hashval as
8519ccc37e3SMark Peek * its hash value. */
8529ccc37e3SMark Peek static struct Hist *
findHistHashTable(struct wordent * lp,unsigned hashval)8539ccc37e3SMark Peek findHistHashTable(struct wordent *lp, unsigned hashval)
8549ccc37e3SMark Peek {
8559ccc37e3SMark Peek unsigned deleted = 0; /* number of deleted entries skipped */
8569ccc37e3SMark Peek unsigned hi = hashval;
8579ccc37e3SMark Peek struct Hist *hp;
8589ccc37e3SMark Peek if (!histHashTable)
8599ccc37e3SMark Peek return NULL;
8609ccc37e3SMark Peek while ((hi = hash2tableIndex(hi, histHashTableLength)),
8619ccc37e3SMark Peek (hp = histHashTable[hi]) != emptyHTE) {
8629ccc37e3SMark Peek if (hp == deletedHTE)
8639ccc37e3SMark Peek deleted++;
8649ccc37e3SMark Peek else if (hp->Hhash == hashval && heq(lp, &(hp->Hlex)))
8659ccc37e3SMark Peek return hp;
8669ccc37e3SMark Peek if (deleted > (histHashTableLength>>4)) {
8679ccc37e3SMark Peek /* lots of deletes, so we need a sparser table. */
8689ccc37e3SMark Peek discardHistHashTable();
8699ccc37e3SMark Peek createHistHashTable(histHashTableLength);
8709ccc37e3SMark Peek return findHistHashTable(lp, hashval);
8719ccc37e3SMark Peek }
8729ccc37e3SMark Peek hi++; /* linear rehash */
8739ccc37e3SMark Peek }
8749ccc37e3SMark Peek return NULL;
8759ccc37e3SMark Peek }
8769ccc37e3SMark Peek
8779ccc37e3SMark Peek /* When merge semantics are in use, find the approximate predecessor for the
8789ccc37e3SMark Peek * new entry, so that the Htime entries are decreasing. Return the entry just
8799ccc37e3SMark Peek * before the first entry with equal times, so the caller can check for
8809ccc37e3SMark Peek * duplicates. When pTime is not NULL, use it as a starting point for search,
8819ccc37e3SMark Peek * otherwise search from beginning (largest time value) of history list. */
8829ccc37e3SMark Peek PG_STATIC struct Hist *
mergeInsertionPoint(struct Hist * np,struct Hist * pTime)8839ccc37e3SMark Peek mergeInsertionPoint(
8849ccc37e3SMark Peek struct Hist *np, /* new entry to be inserted */
8859ccc37e3SMark Peek struct Hist *pTime) /* hint about where to insert */
8869ccc37e3SMark Peek {
8879ccc37e3SMark Peek struct Hist *pp, *p;
8889ccc37e3SMark Peek if (histTail && histTail->Htime >= np->Htime)
8899ccc37e3SMark Peek pTime = histTail; /* new entry goes at the end */
8909ccc37e3SMark Peek if (histMerg && histMerg != &Histlist && histMerg != Histlist.Hnext) {
8919ccc37e3SMark Peek /* Check above and below previous insertion point, in case we're adding
8929ccc37e3SMark Peek * sequential times in the middle of the list (e.g. history -M). */
8939ccc37e3SMark Peek if (histMerg->Htime >= np->Htime)
8949ccc37e3SMark Peek pTime = histMerg;
8959ccc37e3SMark Peek else if (histMerg->Hprev->Htime >= np->Htime)
8969ccc37e3SMark Peek pTime = histMerg->Hprev;
8979ccc37e3SMark Peek }
8989ccc37e3SMark Peek if (pTime) {
8999ccc37e3SMark Peek /* With hint, search up the list until Htime is greater. We skip past
9009ccc37e3SMark Peek * the equal ones, too, so our caller can elide duplicates. */
9019ccc37e3SMark Peek pp = pTime;
9029ccc37e3SMark Peek while (pp != &Histlist && pp->Htime <= np->Htime)
9039ccc37e3SMark Peek pp = pp->Hprev;
9049ccc37e3SMark Peek } else
9059ccc37e3SMark Peek pp = &Histlist;
9069ccc37e3SMark Peek /* Search down the list while current entry's time is too large. */
9079ccc37e3SMark Peek while ((p = pp->Hnext) && (p->Htime > np->Htime))
9089ccc37e3SMark Peek pp = p; /* advance insertion point */
9099ccc37e3SMark Peek /* Remember recent position as hint for next time */
9109ccc37e3SMark Peek histMerg = pp;
9119ccc37e3SMark Peek return pp;
9129ccc37e3SMark Peek }
9139ccc37e3SMark Peek
9149ccc37e3SMark Peek /* Bubble Hnum & Href in new entry down to pp through earlier part of list. */
bubbleHnumHrefDown(struct Hist * np,struct Hist * pp)9159ccc37e3SMark Peek PG_STATIC void bubbleHnumHrefDown(struct Hist *np, struct Hist *pp)
9169ccc37e3SMark Peek {
9179ccc37e3SMark Peek struct Hist *p;
9189ccc37e3SMark Peek for (p = Histlist.Hnext; p != pp->Hnext; p = p->Hnext) {
9199ccc37e3SMark Peek /* swap Hnum & Href values of p and np. */
9209ccc37e3SMark Peek int n = p->Hnum, r = p->Href;
9219ccc37e3SMark Peek p->Hnum = np->Hnum; p->Href = np->Href;
9229ccc37e3SMark Peek np->Hnum = n; np->Href = r;
9239ccc37e3SMark Peek }
9249ccc37e3SMark Peek }
9259ccc37e3SMark Peek
9269ccc37e3SMark Peek /* Enter new command into the history list according to current settings. */
9279ccc37e3SMark Peek struct Hist *
enthist(int event,struct wordent * lp,int docopy,int mflg,int hlen)9289ccc37e3SMark Peek enthist(
9299ccc37e3SMark Peek int event, /* newly incremented global eventno */
9309ccc37e3SMark Peek struct wordent *lp,
9319ccc37e3SMark Peek int docopy,
9329ccc37e3SMark Peek int mflg, /* true if merge requested */
93319d2e3deSDmitry Chagin int hlen) /* -1 if unknown */
9349ccc37e3SMark Peek {
9359ccc37e3SMark Peek struct Hist *p = NULL, *pp = &Histlist, *pTime = NULL;
93623338178SMark Peek struct Hist *np;
93745e5710bSMark Peek const Char *dp;
9389ccc37e3SMark Peek unsigned lpHash = 0; /* non-zero if hashing entries */
939c80476e4SDavid E. O'Brien
940c80476e4SDavid E. O'Brien if ((dp = varval(STRhistdup)) != STRNULL) {
941c80476e4SDavid E. O'Brien if (eq(dp, STRerase)) {
942c80476e4SDavid E. O'Brien /* masaoki@akebono.tky.hp.com (Kobayashi Masaoki) */
94319d2e3deSDmitry Chagin createHistHashTable(hlen);
9449ccc37e3SMark Peek lpHash = hashhist(lp);
9459ccc37e3SMark Peek assert(lpHash != 0);
9469ccc37e3SMark Peek p = findHistHashTable(lp, lpHash);
9479ccc37e3SMark Peek if (p) {
948c80476e4SDavid E. O'Brien if (Htime != 0 && p->Htime > Htime)
949c80476e4SDavid E. O'Brien Htime = p->Htime;
9509ccc37e3SMark Peek /* If we are merging, and the old entry is at the place we want
9519ccc37e3SMark Peek * to insert the new entry, then remember the place. */
9529ccc37e3SMark Peek if (mflg && Htime != 0 && p->Hprev->Htime >= Htime)
9539ccc37e3SMark Peek pTime = p->Hprev;
9549ccc37e3SMark Peek if (!fastMergeErase)
9559ccc37e3SMark Peek renumberHist(p); /* Reset Href of subsequent entries */
9569ccc37e3SMark Peek hremove(p);
957c80476e4SDavid E. O'Brien hfree(p);
9589ccc37e3SMark Peek p = NULL; /* so new entry is allocated below */
959c80476e4SDavid E. O'Brien }
960c80476e4SDavid E. O'Brien }
961c80476e4SDavid E. O'Brien else if (eq(dp, STRall)) {
96219d2e3deSDmitry Chagin createHistHashTable(hlen);
9639ccc37e3SMark Peek lpHash = hashhist(lp);
9649ccc37e3SMark Peek assert(lpHash != 0);
9659ccc37e3SMark Peek p = findHistHashTable(lp, lpHash);
9669ccc37e3SMark Peek if (p) /* p!=NULL, only update this entry's Htime below */
9679ccc37e3SMark Peek eventno--; /* not adding a new event */
968c80476e4SDavid E. O'Brien }
969c80476e4SDavid E. O'Brien else if (eq(dp, STRprev)) {
970c80476e4SDavid E. O'Brien if (pp->Hnext && heq(lp, &(pp->Hnext->Hlex))) {
971c80476e4SDavid E. O'Brien p = pp->Hnext;
972c80476e4SDavid E. O'Brien eventno--;
973c80476e4SDavid E. O'Brien }
974c80476e4SDavid E. O'Brien }
975c80476e4SDavid E. O'Brien }
976c80476e4SDavid E. O'Brien
97745e5710bSMark Peek np = p ? p : xmalloc(sizeof(*np));
978c80476e4SDavid E. O'Brien
979c80476e4SDavid E. O'Brien /* Pick up timestamp set by lex() in Htime if reading saved history */
98045e5710bSMark Peek if (Htime != 0) {
981c80476e4SDavid E. O'Brien np->Htime = Htime;
982c80476e4SDavid E. O'Brien Htime = 0;
983c80476e4SDavid E. O'Brien }
984c80476e4SDavid E. O'Brien else
985c80476e4SDavid E. O'Brien (void) time(&(np->Htime));
986c80476e4SDavid E. O'Brien
987c80476e4SDavid E. O'Brien if (p == np)
9889ccc37e3SMark Peek return np; /* reused existing entry */
989c80476e4SDavid E. O'Brien
9909ccc37e3SMark Peek /* Initialize the new entry. */
991c80476e4SDavid E. O'Brien np->Hnum = np->Href = event;
992c80476e4SDavid E. O'Brien if (docopy) {
993c80476e4SDavid E. O'Brien copylex(&np->Hlex, lp);
994c80476e4SDavid E. O'Brien if (histvalid)
99545e5710bSMark Peek np->histline = Strsave(histline.s);
996c80476e4SDavid E. O'Brien else
997c80476e4SDavid E. O'Brien np->histline = NULL;
998c80476e4SDavid E. O'Brien }
999c80476e4SDavid E. O'Brien else {
1000c80476e4SDavid E. O'Brien np->Hlex.next = lp->next;
1001c80476e4SDavid E. O'Brien lp->next->prev = &np->Hlex;
1002c80476e4SDavid E. O'Brien np->Hlex.prev = lp->prev;
1003c80476e4SDavid E. O'Brien lp->prev->next = &np->Hlex;
1004c80476e4SDavid E. O'Brien np->histline = NULL;
1005c80476e4SDavid E. O'Brien }
10069ccc37e3SMark Peek np->Hhash = 0;
10079ccc37e3SMark Peek
10089ccc37e3SMark Peek /* The head of history list is the default insertion point.
10099ccc37e3SMark Peek If merging, advance insertion point, in pp, according to Htime. */
10109ccc37e3SMark Peek /* XXX -- In histdup=all, Htime values can be non-monotonic. */
10119ccc37e3SMark Peek if (mflg) { /* merge according to np->Htime */
10129ccc37e3SMark Peek pp = mergeInsertionPoint(np, pTime);
10139ccc37e3SMark Peek for (p = pp->Hnext; p && p->Htime == np->Htime; pp = p, p = p->Hnext) {
10149ccc37e3SMark Peek if (heq(&p->Hlex, &np->Hlex)) {
10159ccc37e3SMark Peek eventno--; /* duplicate, so don't add new event */
1016c80476e4SDavid E. O'Brien hfree(np);
1017c80476e4SDavid E. O'Brien return (p);
1018c80476e4SDavid E. O'Brien }
1019c80476e4SDavid E. O'Brien }
10209ccc37e3SMark Peek /* pp is now the last entry with time >= to np. */
10219ccc37e3SMark Peek if (!fastMergeErase) { /* renumber at end of loadhist */
10229ccc37e3SMark Peek /* Before inserting np after pp, bubble its Hnum & Href values down
10239ccc37e3SMark Peek * through the earlier part of list. */
10249ccc37e3SMark Peek bubbleHnumHrefDown(np, pp);
1025c80476e4SDavid E. O'Brien }
1026c80476e4SDavid E. O'Brien }
10279ccc37e3SMark Peek else
10289ccc37e3SMark Peek pp = &Histlist; /* insert at beginning of history */
10299ccc37e3SMark Peek hinsert(np, pp);
103019d2e3deSDmitry Chagin if (lpHash && hlen != 0) /* erase & all modes use hash table */
10319ccc37e3SMark Peek insertHistHashTable(np, lpHash);
10329ccc37e3SMark Peek else
10339ccc37e3SMark Peek discardHistHashTable();
1034c80476e4SDavid E. O'Brien return (np);
1035c80476e4SDavid E. O'Brien }
1036c80476e4SDavid E. O'Brien
1037c80476e4SDavid E. O'Brien static void
hfree(struct Hist * hp)103845e5710bSMark Peek hfree(struct Hist *hp)
1039c80476e4SDavid E. O'Brien {
10409ccc37e3SMark Peek assert(hp != histMerg);
10419ccc37e3SMark Peek if (hp->Hhash)
10429ccc37e3SMark Peek removeHistHashTable(hp);
1043c80476e4SDavid E. O'Brien freelex(&hp->Hlex);
1044c80476e4SDavid E. O'Brien if (hp->histline)
104545e5710bSMark Peek xfree(hp->histline);
104645e5710bSMark Peek xfree(hp);
1047c80476e4SDavid E. O'Brien }
1048c80476e4SDavid E. O'Brien
10499ccc37e3SMark Peek PG_STATIC void
phist(struct Hist * hp,int hflg)105045e5710bSMark Peek phist(struct Hist *hp, int hflg)
1051c80476e4SDavid E. O'Brien {
105219d2e3deSDmitry Chagin if (hp->Href < 0)
105319d2e3deSDmitry Chagin return;
1054c80476e4SDavid E. O'Brien if (hflg & HIST_ONLY) {
105545e5710bSMark Peek int old_output_raw;
105645e5710bSMark Peek
1057c80476e4SDavid E. O'Brien /*
1058c80476e4SDavid E. O'Brien * Control characters have to be written as is (output_raw).
1059c80476e4SDavid E. O'Brien * This way one can preserve special characters (like tab) in
1060c80476e4SDavid E. O'Brien * the history file.
1061c80476e4SDavid E. O'Brien * From: mveksler@vnet.ibm.com (Veksler Michael)
1062c80476e4SDavid E. O'Brien */
106345e5710bSMark Peek old_output_raw = output_raw;
1064c80476e4SDavid E. O'Brien output_raw = 1;
106545e5710bSMark Peek cleanup_push(&old_output_raw, output_raw_restore);
1066c80476e4SDavid E. O'Brien if (hflg & HIST_TIME)
1067c80476e4SDavid E. O'Brien /*
1068c80476e4SDavid E. O'Brien * Make file entry with history time in format:
1069c80476e4SDavid E. O'Brien * "+NNNNNNNNNN" (10 digits, left padded with ascii '0')
1070c80476e4SDavid E. O'Brien */
1071c80476e4SDavid E. O'Brien
107245e5710bSMark Peek xprintf("#+%010lu\n", (unsigned long)hp->Htime);
1073c80476e4SDavid E. O'Brien
1074c80476e4SDavid E. O'Brien if (HistLit && hp->histline)
1075c80476e4SDavid E. O'Brien xprintf("%S\n", hp->histline);
1076c80476e4SDavid E. O'Brien else
1077c80476e4SDavid E. O'Brien prlex(&hp->Hlex);
107845e5710bSMark Peek cleanup_until(&old_output_raw);
1079c80476e4SDavid E. O'Brien }
1080c80476e4SDavid E. O'Brien else {
1081c80476e4SDavid E. O'Brien Char *cp = str2short("%h\t%T\t%R\n");
108245e5710bSMark Peek Char *p;
1083c80476e4SDavid E. O'Brien struct varent *vp = adrof(STRhistory);
1084c80476e4SDavid E. O'Brien
108529301572SMark Peek if (vp && vp->vec != NULL && vp->vec[0] && vp->vec[1])
1086c80476e4SDavid E. O'Brien cp = vp->vec[1];
1087c80476e4SDavid E. O'Brien
108845e5710bSMark Peek p = tprintf(FMT_HISTORY, cp, NULL, hp->Htime, hp);
108945e5710bSMark Peek cleanup_push(p, xfree);
109045e5710bSMark Peek for (cp = p; *cp;)
109123338178SMark Peek xputwchar(*cp++);
109245e5710bSMark Peek cleanup_until(p);
1093c80476e4SDavid E. O'Brien }
1094c80476e4SDavid E. O'Brien }
1095c80476e4SDavid E. O'Brien
10969ccc37e3SMark Peek PG_STATIC void
dophist(int n,int hflg)10979ccc37e3SMark Peek dophist(int n, int hflg)
10989ccc37e3SMark Peek {
10999ccc37e3SMark Peek struct Hist *hp;
11009ccc37e3SMark Peek if (setintr) {
11019ccc37e3SMark Peek int old_pintr_disabled;
11029ccc37e3SMark Peek
11039ccc37e3SMark Peek pintr_push_enable(&old_pintr_disabled);
11049ccc37e3SMark Peek cleanup_until(&old_pintr_disabled);
11059ccc37e3SMark Peek }
11069ccc37e3SMark Peek if ((hflg & HIST_REV) == 0) {
11079ccc37e3SMark Peek /* Since the history list is stored most recent first, non-reversing
11089ccc37e3SMark Peek * print needs to print (backwards) up the list. */
11099ccc37e3SMark Peek if ((unsigned)n >= histCount)
11109ccc37e3SMark Peek hp = histTail;
11119ccc37e3SMark Peek else {
11129ccc37e3SMark Peek for (hp = Histlist.Hnext;
11139ccc37e3SMark Peek --n > 0 && hp->Hnext != NULL;
11149ccc37e3SMark Peek hp = hp->Hnext)
11159ccc37e3SMark Peek ;
11169ccc37e3SMark Peek }
11179ccc37e3SMark Peek if (hp == NULL)
11189ccc37e3SMark Peek return; /* nothing to print */
11199ccc37e3SMark Peek for (; hp != &Histlist; hp = hp->Hprev)
11209ccc37e3SMark Peek phist(hp, hflg);
11219ccc37e3SMark Peek } else {
11229ccc37e3SMark Peek for (hp = Histlist.Hnext; n-- > 0 && hp != NULL; hp = hp->Hnext)
11239ccc37e3SMark Peek phist(hp, hflg);
11249ccc37e3SMark Peek }
11259ccc37e3SMark Peek }
11269ccc37e3SMark Peek
11279ccc37e3SMark Peek /*ARGSUSED*/
11289ccc37e3SMark Peek void
dohist(Char ** vp,struct command * c)11299ccc37e3SMark Peek dohist(Char **vp, struct command *c)
11309ccc37e3SMark Peek {
11319ccc37e3SMark Peek int n, hflg = 0;
11329ccc37e3SMark Peek
11339ccc37e3SMark Peek USE(c);
11349ccc37e3SMark Peek if (getn(varval(STRhistory)) == 0)
11359ccc37e3SMark Peek return;
11369ccc37e3SMark Peek while (*++vp && **vp == '-') {
11379ccc37e3SMark Peek Char *vp2 = *vp;
11389ccc37e3SMark Peek
11399ccc37e3SMark Peek while (*++vp2)
11409ccc37e3SMark Peek switch (*vp2) {
11419ccc37e3SMark Peek case 'c':
11429ccc37e3SMark Peek hflg |= HIST_CLEAR;
11439ccc37e3SMark Peek break;
11449ccc37e3SMark Peek case 'h':
11459ccc37e3SMark Peek hflg |= HIST_ONLY;
11469ccc37e3SMark Peek break;
11479ccc37e3SMark Peek case 'r':
11489ccc37e3SMark Peek hflg |= HIST_REV;
11499ccc37e3SMark Peek break;
11509ccc37e3SMark Peek case 'S':
11519ccc37e3SMark Peek hflg |= HIST_SAVE;
11529ccc37e3SMark Peek break;
11539ccc37e3SMark Peek case 'L':
11549ccc37e3SMark Peek hflg |= HIST_LOAD;
11559ccc37e3SMark Peek break;
11569ccc37e3SMark Peek case 'M':
11579ccc37e3SMark Peek hflg |= HIST_MERGE;
11589ccc37e3SMark Peek break;
11599ccc37e3SMark Peek case 'T':
11609ccc37e3SMark Peek hflg |= HIST_TIME;
11619ccc37e3SMark Peek break;
11629ccc37e3SMark Peek default:
11639ccc37e3SMark Peek stderror(ERR_HISTUS, "chrSLMT");
11649ccc37e3SMark Peek break;
11659ccc37e3SMark Peek }
11669ccc37e3SMark Peek }
11679ccc37e3SMark Peek if (hflg & HIST_CLEAR) {
11689ccc37e3SMark Peek struct Hist *np, *hp;
11699ccc37e3SMark Peek for (hp = &Histlist; (np = hp->Hnext) != NULL;)
11709ccc37e3SMark Peek hremove(np), hfree(np);
11719ccc37e3SMark Peek }
11729ccc37e3SMark Peek
11739ccc37e3SMark Peek if (hflg & (HIST_LOAD | HIST_MERGE))
11749ccc37e3SMark Peek loadhist(*vp, (hflg & HIST_MERGE) ? 1 : 0);
11759ccc37e3SMark Peek else if (hflg & HIST_SAVE)
11769ccc37e3SMark Peek rechist(*vp, 1);
11779ccc37e3SMark Peek else {
11789ccc37e3SMark Peek if (*vp)
11799ccc37e3SMark Peek n = getn(*vp);
11809ccc37e3SMark Peek else {
11819ccc37e3SMark Peek n = getn(varval(STRhistory));
11829ccc37e3SMark Peek }
11839ccc37e3SMark Peek dophist(n, hflg);
11849ccc37e3SMark Peek }
11859ccc37e3SMark Peek }
11869ccc37e3SMark Peek
1187c80476e4SDavid E. O'Brien
118845e5710bSMark Peek char *
fmthist(int fmt,ptr_t ptr)118945e5710bSMark Peek fmthist(int fmt, ptr_t ptr)
1190c80476e4SDavid E. O'Brien {
119145e5710bSMark Peek struct Hist *hp = ptr;
119245e5710bSMark Peek char *buf;
119345e5710bSMark Peek
1194c80476e4SDavid E. O'Brien switch (fmt) {
1195c80476e4SDavid E. O'Brien case 'h':
119645e5710bSMark Peek return xasprintf("%6d", hp->Hnum);
1197c80476e4SDavid E. O'Brien case 'R':
1198c80476e4SDavid E. O'Brien if (HistLit && hp->histline)
119945e5710bSMark Peek return xasprintf("%S", hp->histline);
1200c80476e4SDavid E. O'Brien else {
120145e5710bSMark Peek Char *istr, *ip;
1202c80476e4SDavid E. O'Brien char *p;
120323338178SMark Peek
120445e5710bSMark Peek istr = sprlex(&hp->Hlex);
120545e5710bSMark Peek buf = xmalloc(Strlen(istr) * MB_LEN_MAX + 1);
120645e5710bSMark Peek
120745e5710bSMark Peek for (p = buf, ip = istr; *ip != '\0'; ip++)
120819d2e3deSDmitry Chagin p += one_wctomb(p, *ip);
120945e5710bSMark Peek
121023338178SMark Peek *p = '\0';
121145e5710bSMark Peek xfree(istr);
121245e5710bSMark Peek return buf;
1213c80476e4SDavid E. O'Brien }
1214c80476e4SDavid E. O'Brien default:
121545e5710bSMark Peek buf = xmalloc(1);
1216c80476e4SDavid E. O'Brien buf[0] = '\0';
121745e5710bSMark Peek return buf;
1218c80476e4SDavid E. O'Brien }
1219c80476e4SDavid E. O'Brien }
1220c80476e4SDavid E. O'Brien
122119d2e3deSDmitry Chagin static void
dotlock_cleanup(void * lockpath)122219d2e3deSDmitry Chagin dotlock_cleanup(void* lockpath)
122319d2e3deSDmitry Chagin {
122419d2e3deSDmitry Chagin dot_unlock((char*)lockpath);
122519d2e3deSDmitry Chagin }
122619d2e3deSDmitry Chagin
12279ccc37e3SMark Peek /* Save history before exiting the shell. */
1228c80476e4SDavid E. O'Brien void
rechist(Char * xfname,int ref)1229*6560ac57SDmitry Chagin rechist(Char *xfname, int ref)
1230c80476e4SDavid E. O'Brien {
123119d2e3deSDmitry Chagin Char *snum, *rs;
12325224c2a3SDmitry Chagin int fp, ftmp, oldidfds, ophup_disabled;
1233c80476e4SDavid E. O'Brien struct varent *shist;
123419d2e3deSDmitry Chagin char path[MAXPATHLEN];
123519d2e3deSDmitry Chagin struct stat st;
1236*6560ac57SDmitry Chagin static Char *fname;
1237c80476e4SDavid E. O'Brien static Char *dumphist[] = {STRhistory, STRmhT, 0, 0};
1238c80476e4SDavid E. O'Brien
1239c80476e4SDavid E. O'Brien if (fname == NULL && !ref)
1240c80476e4SDavid E. O'Brien return;
12415224c2a3SDmitry Chagin
1242*6560ac57SDmitry Chagin fname = xfname;
12435224c2a3SDmitry Chagin ophup_disabled = phup_disabled;
12445224c2a3SDmitry Chagin phup_disabled = 1;
12455224c2a3SDmitry Chagin
1246c80476e4SDavid E. O'Brien /*
1247c80476e4SDavid E. O'Brien * If $savehist is just set, we use the value of $history
1248c80476e4SDavid E. O'Brien * else we use the value in $savehist
1249c80476e4SDavid E. O'Brien */
1250c80476e4SDavid E. O'Brien if (((snum = varval(STRsavehist)) == STRNULL) &&
1251c80476e4SDavid E. O'Brien ((snum = varval(STRhistory)) == STRNULL))
1252c80476e4SDavid E. O'Brien snum = STRmaxint;
1253c80476e4SDavid E. O'Brien
1254c80476e4SDavid E. O'Brien
1255c80476e4SDavid E. O'Brien if (fname == NULL) {
1256c80476e4SDavid E. O'Brien if ((fname = varval(STRhistfile)) == STRNULL)
1257c80476e4SDavid E. O'Brien fname = Strspl(varval(STRhome), &STRtildothist[1]);
1258c80476e4SDavid E. O'Brien else
1259c80476e4SDavid E. O'Brien fname = Strsave(fname);
1260c80476e4SDavid E. O'Brien }
1261c80476e4SDavid E. O'Brien else
1262c80476e4SDavid E. O'Brien fname = globone(fname, G_ERROR);
126345e5710bSMark Peek cleanup_push(fname, xfree);
1264c80476e4SDavid E. O'Brien
1265c80476e4SDavid E. O'Brien /*
1266c80476e4SDavid E. O'Brien * The 'savehist merge' feature is intended for an environment
126745e5710bSMark Peek * with numerous shells being in simultaneous use. Imagine
1268c80476e4SDavid E. O'Brien * any kind of window system. All these shells 'share' the same
1269c80476e4SDavid E. O'Brien * ~/.history file for recording their command line history.
127019d2e3deSDmitry Chagin * We try to handle the case of multiple shells trying to merge
127119d2e3deSDmitry Chagin * histories at the same time, by creating semi-unique filenames
127219d2e3deSDmitry Chagin * and saving the history there first and then trying to rename
127319d2e3deSDmitry Chagin * them in the proper history file.
1274c80476e4SDavid E. O'Brien *
1275c80476e4SDavid E. O'Brien * Users that like to nuke their environment require here an atomic
127619d2e3deSDmitry Chagin * loadhist-creat-dohist(dumphist)-close sequence which is given
127719d2e3deSDmitry Chagin * by optional lock parameter to savehist.
1278c80476e4SDavid E. O'Brien *
1279c80476e4SDavid E. O'Brien * jw.
1280c80476e4SDavid E. O'Brien */
1281c80476e4SDavid E. O'Brien /*
1282c80476e4SDavid E. O'Brien * We need the didfds stuff before loadhist otherwise
1283c80476e4SDavid E. O'Brien * exec in a script will fail to print if merge is set.
1284c80476e4SDavid E. O'Brien * From: mveksler@iil.intel.com (Veksler Michael)
1285c80476e4SDavid E. O'Brien */
1286c80476e4SDavid E. O'Brien oldidfds = didfds;
1287c80476e4SDavid E. O'Brien didfds = 0;
128819d2e3deSDmitry Chagin if ((shist = adrof(STRsavehist)) != NULL && shist->vec != NULL) {
128919d2e3deSDmitry Chagin size_t i;
129019d2e3deSDmitry Chagin int merge = 0, lock = 0;
12919ccc37e3SMark Peek
129219d2e3deSDmitry Chagin for (i = 1; shist->vec[i]; i++) {
129319d2e3deSDmitry Chagin if (eq(shist->vec[i], STRmerge))
129419d2e3deSDmitry Chagin merge++;
129519d2e3deSDmitry Chagin if (eq(shist->vec[i], STRlock))
129619d2e3deSDmitry Chagin lock++;
129719d2e3deSDmitry Chagin }
129819d2e3deSDmitry Chagin
129919d2e3deSDmitry Chagin if (merge) {
1300d803a9d0SBrooks Davis jmp_buf_t osetexit;
130119d2e3deSDmitry Chagin if (lock) {
130219d2e3deSDmitry Chagin #ifndef WINNT_NATIVE
130319d2e3deSDmitry Chagin char *lockpath = strsave(short2str(fname));
130419d2e3deSDmitry Chagin cleanup_push(lockpath, xfree);
130519d2e3deSDmitry Chagin /* Poll in 100 miliseconds interval to obtain the lock. */
130619d2e3deSDmitry Chagin if ((dot_lock(lockpath, 100) == 0))
130719d2e3deSDmitry Chagin cleanup_push(lockpath, dotlock_cleanup);
130819d2e3deSDmitry Chagin #endif
130919d2e3deSDmitry Chagin }
1310d803a9d0SBrooks Davis getexit(osetexit);
13115224c2a3SDmitry Chagin if (setexit() == 0)
131219d2e3deSDmitry Chagin loadhist(fname, 1);
1313d803a9d0SBrooks Davis resexit(osetexit);
131419d2e3deSDmitry Chagin }
131519d2e3deSDmitry Chagin }
131619d2e3deSDmitry Chagin rs = randsuf();
131719d2e3deSDmitry Chagin xsnprintf(path, sizeof(path), "%S.%S", fname, rs);
131819d2e3deSDmitry Chagin xfree(rs);
131919d2e3deSDmitry Chagin
132019d2e3deSDmitry Chagin fp = xcreat(path, 0600);
1321c80476e4SDavid E. O'Brien if (fp == -1) {
1322c80476e4SDavid E. O'Brien didfds = oldidfds;
132319d2e3deSDmitry Chagin cleanup_until(fname);
13245224c2a3SDmitry Chagin phup_disabled = ophup_disabled;
1325c80476e4SDavid E. O'Brien return;
1326c80476e4SDavid E. O'Brien }
132719d2e3deSDmitry Chagin /* Try to preserve ownership and permissions of the original history file */
132819d2e3deSDmitry Chagin #ifndef WINNT_NATIVE
132919d2e3deSDmitry Chagin if (stat(short2str(fname), &st) != -1) {
133019d2e3deSDmitry Chagin TCSH_IGNORE(fchown(fp, st.st_uid, st.st_gid));
133119d2e3deSDmitry Chagin TCSH_IGNORE(fchmod(fp, st.st_mode));
133219d2e3deSDmitry Chagin }
133319d2e3deSDmitry Chagin #else
133419d2e3deSDmitry Chagin UNREFERENCED_PARAMETER(st);
133519d2e3deSDmitry Chagin #endif
1336c80476e4SDavid E. O'Brien ftmp = SHOUT;
1337c80476e4SDavid E. O'Brien SHOUT = fp;
1338c80476e4SDavid E. O'Brien dumphist[2] = snum;
1339c80476e4SDavid E. O'Brien dohist(dumphist, NULL);
134045e5710bSMark Peek xclose(fp);
1341c80476e4SDavid E. O'Brien SHOUT = ftmp;
1342c80476e4SDavid E. O'Brien didfds = oldidfds;
1343cc698b49SBrooks Davis #ifndef WINNT_NATIVE
134419d2e3deSDmitry Chagin (void)rename(path, short2str(fname));
1345cc698b49SBrooks Davis #else
1346cc698b49SBrooks Davis (void)ReplaceFile(short2str(fname), path, NULL, 0, NULL, NULL);
1347cc698b49SBrooks Davis #endif
134819d2e3deSDmitry Chagin cleanup_until(fname);
13495224c2a3SDmitry Chagin phup_disabled = ophup_disabled;
1350c80476e4SDavid E. O'Brien }
1351c80476e4SDavid E. O'Brien
1352c80476e4SDavid E. O'Brien
13539ccc37e3SMark Peek /* This is the entry point for loading history data from a file. */
1354c80476e4SDavid E. O'Brien void
loadhist(Char * fname,int mflg)135545e5710bSMark Peek loadhist(Char *fname, int mflg)
1356c80476e4SDavid E. O'Brien {
1357c80476e4SDavid E. O'Brien static Char *loadhist_cmd[] = {STRsource, NULL, NULL, NULL};
1358c80476e4SDavid E. O'Brien loadhist_cmd[1] = mflg ? STRmm : STRmh;
1359c80476e4SDavid E. O'Brien
1360c80476e4SDavid E. O'Brien if (fname != NULL)
1361c80476e4SDavid E. O'Brien loadhist_cmd[2] = fname;
1362c80476e4SDavid E. O'Brien else if ((fname = varval(STRhistfile)) != STRNULL)
1363c80476e4SDavid E. O'Brien loadhist_cmd[2] = fname;
1364c80476e4SDavid E. O'Brien else
1365c80476e4SDavid E. O'Brien loadhist_cmd[2] = STRtildothist;
1366c80476e4SDavid E. O'Brien
1367c80476e4SDavid E. O'Brien dosource(loadhist_cmd, NULL);
13689ccc37e3SMark Peek
13699ccc37e3SMark Peek /* During history merging (enthist sees mflg set), we disable management of
13709ccc37e3SMark Peek * Hnum and Href (because fastMergeErase is true). So now reset all the
13719ccc37e3SMark Peek * values based on the final ordering of the history list. */
13729ccc37e3SMark Peek if (mflg) {
13739ccc37e3SMark Peek int n = eventno;
13749ccc37e3SMark Peek struct Hist *hp = &Histlist;
13759ccc37e3SMark Peek while ((hp = hp->Hnext))
13769ccc37e3SMark Peek hp->Hnum = hp->Href = n--;
13779ccc37e3SMark Peek }
1378c80476e4SDavid E. O'Brien }
137919d2e3deSDmitry Chagin
138019d2e3deSDmitry Chagin void
sethistory(int n)138119d2e3deSDmitry Chagin sethistory(int n)
138219d2e3deSDmitry Chagin {
138319d2e3deSDmitry Chagin histlen = n;
138419d2e3deSDmitry Chagin discardExcess(histlen);
138519d2e3deSDmitry Chagin }
1386