xref: /freebsd-src/contrib/tcsh/sh.hist.c (revision 6560ac57ce879857203bc456cdc3849808dc0700)
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