xref: /dragonfly/contrib/tcsh-6/sh.hist.c (revision d6ab524c)
1 /*
2  * sh.hist.c: Shell history expansions and substitutions
3  */
4 /*-
5  * Copyright (c) 1980, 1991 The Regents of the University of California.
6  * All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  * 3. Neither the name of the University nor the names of its contributors
17  *    may be used to endorse or promote products derived from this software
18  *    without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
21  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
24  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30  * SUCH DAMAGE.
31  */
32 #include "sh.h"
33 #include <stdio.h>	/* for rename(2), grr. */
34 #include <assert.h>
35 #include "tc.h"
36 #include "dotlock.h"
37 
38 extern int histvalid;
39 extern struct Strbuf histline;
40 Char HistLit = 0;
41 
42 static	int	heq	(const struct wordent *, const struct wordent *);
43 static	void	hfree	(struct Hist *);
44 
45 #define HIST_ONLY	0x01
46 #define HIST_SAVE	0x02
47 #define HIST_LOAD	0x04
48 #define HIST_REV	0x08
49 #define HIST_CLEAR	0x10
50 #define HIST_MERGE	0x20
51 #define HIST_TIME	0x40
52 
53 /*
54  * C shell
55  */
56 
57 /* Static functions don't show up in gprof summaries.  So eliminate "static"
58  * modifier from some frequently called functions. */
59 #ifdef PROF
60 #define PG_STATIC
61 #else
62 #define PG_STATIC static
63 #endif
64 
65 /* #define DEBUG_HIST 1 */
66 
67 static const int fastMergeErase = 1;
68 static unsigned histCount = 0;		/* number elements on history list */
69 static int histlen = 0;
70 static struct Hist *histTail = NULL;     /* last element on history list */
71 static struct Hist *histMerg = NULL;	 /* last element merged by Htime */
72 
73 static void insertHistHashTable(struct Hist *, unsigned);
74 
75 /* Insert new element (hp) in history list after specified predecessor (pp). */
76 static void
hinsert(struct Hist * hp,struct Hist * pp)77 hinsert(struct Hist *hp, struct Hist *pp)
78 {
79     struct Hist *fp = pp->Hnext;        /* following element, if any */
80     hp->Hnext = fp, hp->Hprev = pp;
81     pp->Hnext = hp;
82     if (fp)
83         fp->Hprev = hp;
84     else
85         histTail = hp;                  /* meaning hp->Hnext == NULL */
86     histCount++;
87 }
88 
89 /* Remove the entry from the history list. */
90 static void
hremove(struct Hist * hp)91 hremove(struct Hist *hp)
92 {
93     struct Hist *pp = hp->Hprev;
94     assert(pp);                         /* elements always have a previous */
95     pp->Hnext = hp->Hnext;
96     if (hp->Hnext)
97         hp->Hnext->Hprev = pp;
98     else
99         histTail = pp;                  /* we must have been last */
100     if (hp == histMerg)			/* deleting this hint from list */
101 	histMerg = NULL;
102     assert(histCount > 0);
103     histCount--;
104 }
105 
106 /* Prune length of history list to specified size by history variable. */
107 PG_STATIC void
discardExcess(int hlen)108 discardExcess(int hlen)
109 {
110     struct Hist *hp, *np;
111     if (histTail == NULL) {
112         assert(histCount == 0);
113         return;                         /* no entries on history list */
114     }
115     /* Prune dummy entries from the front, then old entries from the back. If
116      * the list is still too long scan the whole list as before.  But only do a
117      * full scan if the list is more than 6% (1/16th) too long. */
118     while (histCount > (unsigned)hlen && (np = Histlist.Hnext)) {
119         if (eventno - np->Href >= hlen || hlen == 0)
120             hremove(np), hfree(np);
121         else
122             break;
123     }
124     while (histCount > (unsigned)hlen && (np = histTail) != &Histlist) {
125         if (eventno - np->Href >= hlen || hlen == 0)
126             hremove(np), hfree(np);
127         else
128             break;
129     }
130     if (histCount - (hlen >> 4) <= (unsigned)hlen)
131 	return;				/* don't bother doing the full scan */
132     for (hp = &Histlist; histCount > (unsigned)hlen &&
133 	(np = hp->Hnext) != NULL;)
134         if (eventno - np->Href >= hlen || hlen == 0)
135             hremove(np), hfree(np);
136         else
137             hp = np;
138 }
139 
140 /* Add the command "sp" to the history list. */
141 void
savehist(struct wordent * sp,int mflg)142 savehist(
143   struct wordent *sp,
144   int mflg)				/* true if -m (merge) specified */
145 {
146     /* throw away null lines */
147     if (sp && sp->next->word[0] == '\n')
148 	return;
149     if (sp)
150         (void) enthist(++eventno, sp, 1, mflg, histlen);
151     discardExcess(histlen);
152 }
153 
154 #define USE_JENKINS_HASH 1
155 /* #define USE_ONE_AT_A_TIME 1 */
156 #undef PRIME_LENGTH			/* no need for good HTL */
157 
158 #ifdef USE_JENKINS_HASH
159 #define hashFcnName "lookup3"
160 /* From:
161    lookup3.c, by Bob Jenkins, May 2006, Public Domain.
162    "...  You can use this free for any purpose.  It's in
163     the public domain.  It has no warranty."
164    http://burtleburtle.net/bob/hash/index.html
165  */
166 
167 #define rot(x,k) (((x)<<(k)) | ((x)>>(32-(k))))
168 #define mix(a,b,c) \
169 { \
170   a -= c;  a ^= rot(c, 4);  c += b; \
171   b -= a;  b ^= rot(a, 6);  a += c; \
172   c -= b;  c ^= rot(b, 8);  b += a; \
173   a -= c;  a ^= rot(c,16);  c += b; \
174   b -= a;  b ^= rot(a,19);  a += c; \
175   c -= b;  c ^= rot(b, 4);  b += a; \
176 }
177 #define final(a,b,c) \
178 { \
179   c ^= b; c -= rot(b,14); \
180   a ^= c; a -= rot(c,11); \
181   b ^= a; b -= rot(a,25); \
182   c ^= b; c -= rot(b,16); \
183   a ^= c; a -= rot(c, 4); \
184   b ^= a; b -= rot(a,14); \
185   c ^= b; c -= rot(b,24); \
186 }
187 
188 struct hashValue		  /* State used to hash a wordend word list. */
189 {
190     uint32_t a, b, c;
191 };
192 
193 /* Set up the internal state */
194 static void
initializeHash(struct hashValue * h)195 initializeHash(struct hashValue *h)
196 {
197     h->a = h->b = h->c = 0xdeadbeef;
198 }
199 
200 /* This does a partial hash of the Chars in a single word.  For efficiency we
201  * include 3 versions of the code to pack Chars into 32-bit words for the
202  * mixing function. */
203 static void
addWordToHash(struct hashValue * h,const Char * word)204 addWordToHash(struct hashValue *h, const Char *word)
205 {
206     uint32_t a = h->a, b = h->b, c = h->c;
207 #ifdef SHORT_STRINGS
208 #define GETK    if ((k = (uChar)*word++) == 0) break
209 #ifdef WIDE_STRINGS
210     assert(sizeof(Char) >= 4);
211     while (1) {
212 	unsigned k;
213 	GETK;
214 	a += k;
215 	GETK;
216 	b += k;
217 	GETK;
218 	c += k;
219 	mix(a, b, c);
220     }
221 #else
222     assert(sizeof(Char) == 2);
223     while (1) {
224 	unsigned k;
225 	GETK;
226 	a += k;
227 	GETK;
228 	a += k << 16;
229 	GETK;
230 	b += k;
231 	GETK;
232 	b += k << 16;
233 	GETK;
234 	c += k;
235 	GETK;
236 	c += k << 16;
237 	mix(a, b, c);
238     }
239 #endif
240 #else
241     assert(sizeof(Char) == 1);
242 #define GETK    if ((k = *word++) == 0) break
243     while (1) {
244 	unsigned k;
245 	GETK;
246 	a += k;
247 	GETK;
248 	a += k << 8;
249 	GETK;
250 	a += k << 16;
251 	GETK;
252 	a += k << 24;
253 	GETK;
254 	b += k;
255 	GETK;
256 	b += k << 8;
257 	GETK;
258 	b += k << 16;
259 	GETK;
260 	b += k << 24;
261 	GETK;
262 	c += k;
263 	GETK;
264 	c += k << 8;
265 	GETK;
266 	c += k << 16;
267 	GETK;
268 	c += k << 24;
269 	mix(a, b, c);
270     }
271 #endif
272     h->a = a, h->b = b, h->c = c;
273 }
274 
275 static void
addCharToHash(struct hashValue * h,Char ch)276 addCharToHash(struct hashValue *h, Char ch)
277 {
278     /* The compiler (gcc -O2) seems to do a good job optimizing this without
279      * explicitly extracting into local variables. */
280     h->a += (uChar)ch;
281     mix(h->a, h->b, h->c);
282 }
283 
284 static uint32_t
finalizeHash(struct hashValue * h)285 finalizeHash(struct hashValue *h)
286 {
287     uint32_t a = h->a, b = h->b, c = h->c;
288     final(a, b, c);
289     return c;
290 }
291 
292 #elif USE_ONE_AT_A_TIME
293 #define hashFcnName "one-at-a-time"
294 /* This one is also from Bob Jenkins, but is slower but simpler than lookup3.
295    "...  The code given here are all public domain."
296    http://burtleburtle.net/bob/hash/doobs.html */
297 
298 #if 0
299 ub4
300 one_at_a_time(char *key, ub4 len)
301 {
302   ub4   hash, i;
303   for (hash=0, i=0; i<len; ++i)
304   {
305     hash += key[i];
306     hash += (hash << 10);
307     hash ^= (hash >> 6);
308   }
309   hash += (hash << 3);
310   hash ^= (hash >> 11);
311   hash += (hash << 15);
312   return (hash & mask);
313 }
314 #endif
315 
316 struct hashValue { uint32_t h; };
317 static void
initializeHash(struct hashValue * h)318 initializeHash(struct hashValue *h)
319 {
320     h->h = 0;
321 }
322 
323 static void
addWordToHash(struct hashValue * h,const Char * word)324 addWordToHash(struct hashValue *h, const Char *word)
325 {
326     unsigned k;
327     uint32_t hash = h->h;
328     while (k = (uChar)*word++)
329 	hash += k, hash += hash << 10, hash ^= hash >> 6;
330     h->h = hash;
331 }
332 
333 static void
addCharToHash(struct hashValue * h,Char c)334 addCharToHash(struct hashValue *h, Char c)
335 {
336     Char b[2] = { c, 0 };
337     addWordToHash(h, b);
338 }
339 
340 static uint32_t
finalizeHash(struct hashValue * h)341 finalizeHash(struct hashValue *h)
342 {
343     unsigned hash = h->h;
344     hash += (hash << 3);
345     hash ^= (hash >> 11);
346     hash += (hash << 15);
347     return hash;
348 }
349 
350 #else
351 #define hashFcnName "add-mul"
352 /* Simple multipy and add hash. */
353 #define PRIME_LENGTH 1			/* need "good" HTL */
354 struct hashValue { uint32_t h; };
355 static void
initializeHash(struct hashValue * h)356 initializeHash(struct hashValue *h)
357 {
358     h->h = 0xe13e2345;
359 }
360 
361 static void
addWordToHash(struct hashValue * h,const Char * word)362 addWordToHash(struct hashValue *h, const Char *word)
363 {
364     unsigned k;
365     uint32_t hash = h->h;
366     while (k = (uChar)*word++)
367 	hash = hash * 0x9e4167b9 + k;
368     h->h = hash;
369 }
370 
371 static void
addCharToHash(struct hashValue * h,Char c)372 addCharToHash(struct hashValue *h, Char c)
373 {
374     h->h = h->h * 0x9e4167b9 + (uChar)c;
375 }
376 
377 static uint32_t
finalizeHash(struct hashValue * h)378 finalizeHash(struct hashValue *h)
379 {
380     return h->h;
381 }
382 #endif
383 
384 static unsigned
hashhist(struct wordent * h0)385 hashhist(struct wordent *h0)
386 {
387     struct hashValue s;
388     struct wordent *firstWord = h0->next;
389     struct wordent *h = firstWord;
390     unsigned hash = 0;
391 
392     initializeHash(&s);
393     for (; h != h0; h = h->next) {
394         if (h->word[0] == '\n')
395             break;                      /* don't hash newline */
396         if (h != firstWord)
397             addCharToHash(&s, ' ');	/* space between words */
398 	addWordToHash(&s, h->word);
399     }
400     hash = finalizeHash(&s);
401     /* Zero means no hash value, so never return zero as a hash value. */
402     return hash ? hash : 0x7fffffff;	/* prime! */
403 }
404 
405 #if 0
406 unsigned
407 hashStr(Char *str)
408 {
409     struct hashValue s;
410     initializeHash(&s);
411     addWordToHash(&s, str);
412     return finalizeHash(&s);
413 }
414 #endif
415 
416 #ifdef PRIME_LENGTH			/* need good HTL */
417 #define hash2tableIndex(hash, len) ((hash) % len)
418 #else
419 #define hash2tableIndex(hash, len) ((hash) & (len-1))
420 #endif
421 
422 /* This code can be enabled to test the above hash functions for speed and
423  * collision avoidance.  The testing is enabled by "occasional" calls to
424  * displayHistStats(), see which. */
425 #ifdef DEBUG_HIST
426 
427 #ifdef BSDTIMES
428 static double
doTiming(int start)429 doTiming(int start) {
430     static struct timeval beginTime;
431     if (start) {
432 	gettimeofday(&beginTime, NULL);
433 	return 0.0;
434     } else {
435 	struct timeval now;
436 	gettimeofday(&now, NULL);
437 	return (now.tv_sec-beginTime.tv_sec) +
438 	    (now.tv_usec-beginTime.tv_usec)/1e6;
439     }
440 }
441 #else
442 static double
doTiming(int start)443 doTiming(int start) {
444     USE(start);
445     return 0.0;
446 }
447 #endif
448 
449 static void
generateHashes(int nChars,unsigned nWords,unsigned samples,unsigned * hashes,unsigned length)450 generateHashes(int nChars, unsigned nWords, unsigned samples, unsigned *hashes,
451     unsigned length)
452 {
453     if (nChars < 1)
454 	return;
455     nWords = (nWords < 1) ? 1 : (nWords > 4) ? 4 : nWords;
456     Char *number = xmalloc((nChars+nWords)*sizeof(Char));
457     struct wordent word[4];
458     struct wordent base = { NULL, &word[0], &word[0] };
459     word[0].word = number, word[0].next = &base, word[0].prev = &base;
460     unsigned w = 0;			/* word number */
461     /* Generate multiple words of length 2, 3, 5, then all the rest. */
462     unsigned wBoundaries[4] = { 2-1, 2+3-1, 2+3+5-1, 0 };
463     /* Ensure the last word has at least 4 Chars in it. */
464     while (nWords >= 2 && nChars < (wBoundaries[nWords-2]+1) + 4)
465 	nWords--;
466     wBoundaries[nWords-1] = 0xffffffff;	/* don't end word past this point */
467     unsigned i;
468     for (i = 0; i<nChars; i++) {
469 	/* In deference to the gawd awful add-mul hash, we won't use the worse
470 	 * case here (setting all Chars to 1), but assume mostly (or at least
471 	 * initially) ASCII data. */
472 	number[i+w] = '!';		/* 0x21 = 33 */
473 
474 	if (i == wBoundaries[w]) {	/* end a word here and move to next */
475 	    w++;			/* next word */
476 	    number[i+w] = 0;		/* terminate */
477 	    word[w].word = &number[i+w+1];
478 	    word[w].next = &base, word[w].prev = &word[w-1];
479 	    word[w-1].next = &word[w], base.prev = &word[w];
480 	}
481     }
482     /* w is the index of the last word actually created. */
483     number[nChars + w] = 0;		/* terminate last word */
484     unsigned timeLimit = !samples;
485     if (samples == 0)
486 	samples = 1000000000;
487     doTiming(1);
488     double sec;
489     for (i = 0; i < samples; i++) {
490 	/* increment 4 digit base 255 number; last characters vary fastest */
491 	unsigned j = nChars-1 + w;
492 	while (1) {
493 	    if (++number[j] != 0)
494 		break;
495 	    /* else reset this digit and proceed to next one */
496 	    number[j] = 1;
497 	    if (&number[j] <= word[w].word)
498 		break;			/* stop at beginning of last word */
499 	    j--;
500 	}
501 	if (word[w].word[0] == '\n')
502 	    word[w].word[0]++;		/* suppress newline character */
503 	unsigned hash = hashhist(&base);
504 	hashes[hash2tableIndex(hash, length)]++;
505 	if (timeLimit && (i & 0x3ffff) == 0x3ffff) {
506 	    sec = doTiming(0);
507 	    if (sec > 10)
508 		break;
509 	}
510     }
511     if (i >= samples)
512 	sec = doTiming(0);
513     else
514 	samples = i;			/* number we actually did */
515     if (sec > 0.01) {
516 	xprintf("Hash %d (%d Char %u words) with %s: %d nsec/hash, %d mcps\n",
517 		samples, nChars, w+1, hashFcnName, (int)((sec/samples)*1e9),
518 		(int)((double)samples*nChars/sec/1e6));
519     }
520 }
521 #endif /* DEBUG_HIST */
522 
523 #ifdef DEBUG_HIST
524 static void
testHash(void)525 testHash(void)
526 {
527     static const Char STRtestHashTimings[] =
528 	{ 't','e','s','t','H','a','s','h','T','i','m','i','n','g','s', 0 };
529     struct varent *vp = adrof(STRtestHashTimings);
530     if (vp && vp->vec) {
531 	unsigned hashes[4];		/* dummy place to put hashes */
532 	Char **vals = vp->vec;
533 	while (*vals) {
534 	    int length = getn(*vals);
535 	    unsigned words =
536 		(length < 5) ? 1 : (length < 25) ? 2 : (length < 75) ? 3 : 4;
537 	    if (length > 0)
538 		generateHashes(length, words, 0, hashes, 4);
539 	    vals++;
540 	}
541     }
542     unsigned length = 1024;
543 #ifdef PRIME_LENGTH			/* need good HTL */
544     length = 1021;
545 #endif
546     unsigned *hashes = xmalloc(length*sizeof(unsigned));
547     memset(hashes, 0, length*sizeof(unsigned));
548     /* Compute collision statistics for half full hashes modulo "length". */
549     generateHashes(4, 1, length/2, hashes, length);
550     /* Evaluate collisions by comparing occupancy rates (mean value 0.5).
551      * One bin for each number of hits. */
552     unsigned bins[155];
553     memset(bins, 0, sizeof(bins));
554     unsigned highest = 0;
555     unsigned i;
556     for (i = 0; i<length; i++) {
557 	unsigned hits = hashes[i];
558 	if (hits >= sizeof(bins)/sizeof(bins[0])) /* clip */
559 	    hits = highest = sizeof(bins)/sizeof(bins[0]) - 1;
560 	if (hits > highest)
561 	    highest = hits;
562 	bins[hits]++;
563     }
564     xprintf("Occupancy of %d buckets by %d hashes %d Chars %d word with %s\n",
565 	    length, length/2, 4, 1, hashFcnName);
566     for (i = 0; i <= highest; i++) {
567 	xprintf(" %d buckets (%d%%) with %d hits\n",
568 		bins[i], bins[i]*100/length, i);
569     }
570     /* Count run lengths to evaluate linear rehashing effectiveness.  Estimate
571      * a little corrupted by edge effects. */
572     memset(bins, 0, sizeof(bins));
573     highest = 0;
574     for (i = 0; hashes[i] == 0; i++);	/* find first occupied bucket */
575     unsigned run = 0;
576     unsigned rehashed = 0;
577     for (; i<length; i++) {
578 	unsigned hits = hashes[i];
579 	if (hits == 0 && rehashed > 0)
580 	    hits = 1 && rehashed--;
581 	else if (hits > 1)
582 	    rehashed += hits-1;
583 	if (hits)
584 	    run++;
585 	else {
586 	    /* a real free slot, count it */
587 	    if (run >= sizeof(bins)/sizeof(bins[0])) /* clip */
588 		run = highest = sizeof(bins)/sizeof(bins[0]) - 1;
589 	    if (run > highest)
590 		highest = run;
591 	    bins[run]++;
592 	    run = 0;
593 	}
594     }
595     /* Ignore the partial run at end as we ignored the beginning. */
596     double merit = 0.0, entries = 0;
597     for (i = 0; i <= highest; i++) {
598 	entries += bins[i]*i;		/* total hashed objects */
599 	merit += bins[i]*i*i;
600     }
601     xprintf("Rehash collision figure of merit %u (ideal=100), run lengths:\n",
602 	    (int)(100.0*merit/entries));
603     for (i = 0; i <= highest; i++) {
604 	if (bins[i] != 0)
605 	    xprintf(" %d runs of length %d buckets\n", bins[i], i);
606     }
607     xfree(hashes);
608 }
609 #endif /* DEBUG_HIST */
610 
611 /* Compares two word lists for equality. */
612 static int
heq(const struct wordent * a0,const struct wordent * b0)613 heq(const struct wordent *a0, const struct wordent *b0)
614 {
615     const struct wordent *a = a0->next, *b = b0->next;
616 
617     for (;;) {
618 	if (Strcmp(a->word, b->word) != 0)
619 	    return 0;
620 	a = a->next;
621 	b = b->next;
622 	if (a == a0)
623 	    return (b == b0) ? 1 : 0;
624 	if (b == b0)
625 	    return 0;
626     }
627 }
628 
629 /* Renumber entries following p, which we will be deleting. */
630 PG_STATIC void
renumberHist(struct Hist * p)631 renumberHist(struct Hist *p)
632 {
633     int n = p->Href;
634     while ((p = p->Hnext))
635         p->Href = n--;
636 }
637 
638 /* The hash table is implemented as an array of pointers to Hist entries.  Each
639  * entry is located in the table using hash2tableIndex() and checking the
640  * following entries in case of a collision (linear rehash).  Free entries in
641  * the table are zero (0, NULL, emptyHTE).  Deleted entries that cannot yet be
642  * freed are set to one (deletedHTE).  The Hist.Hhash member is non-zero iff
643  * the entry is in the hash table.  When the hash table get too full, it is
644  * reallocated to be approximately twice the history length (see
645  * getHashTableSize). */
646 static struct Hist **histHashTable = NULL;
647 static unsigned histHashTableLength = 0; /* number of Hist pointers in table */
648 
649 static struct Hist * const emptyHTE = NULL;
650 static struct Hist * const deletedHTE = (struct Hist *)1;
651 
652 static struct {
653     unsigned insertCount;
654     unsigned removeCount;
655     unsigned rehashes;
656     int deleted;
657 } hashStats;
658 
659 #ifdef DEBUG_HIST
660 void
checkHistHashTable(int print)661 checkHistHashTable(int print)
662 {
663     unsigned occupied = 0;
664     unsigned deleted = 0;
665     unsigned i;
666     for (i = 0; i<histHashTableLength; i++)
667 	if (histHashTable[i] == emptyHTE)
668 	    continue;
669 	else if (histHashTable[i] == deletedHTE)
670 	    deleted++;
671 	else
672 	    occupied++;
673     if (print)
674 	xprintf("  found len %u occupied %u deleted %u\n",
675 		histHashTableLength, occupied, deleted);
676     assert(deleted == hashStats.deleted);
677 }
678 
679 static int doneTest = 0;
680 
681 /* Main entry point for displaying history statistics and hash function
682  * behavior. */
683 void
displayHistStats(const char * reason)684 displayHistStats(const char *reason)
685 {
686     /* Just hash statistics for now. */
687     xprintf("%s history hash table len %u count %u (deleted %d)\n", reason,
688 	    histHashTableLength, histCount, hashStats.deleted);
689     xprintf("  inserts %u rehashes %u%% each\n",
690 	    hashStats.insertCount,
691 	    (hashStats.insertCount
692 	     ? 100*hashStats.rehashes/hashStats.insertCount : 0));
693     xprintf("  removes %u net %u\n",
694 	    hashStats.removeCount,
695 	    hashStats.insertCount - hashStats.removeCount);
696     assert(hashStats.insertCount >= hashStats.removeCount);
697     checkHistHashTable(1);
698     memset(&hashStats, 0, sizeof(hashStats));
699     if (!doneTest) {
700 	testHash();
701 	doneTest = 1;
702     }
703 }
704 #else
705 void
displayHistStats(const char * reason)706 displayHistStats(const char *reason)
707 {
708     USE(reason);
709 }
710 #endif
711 
712 static void
discardHistHashTable(void)713 discardHistHashTable(void)
714 {
715     if (histHashTable == NULL)
716         return;
717     displayHistStats("Discarding");
718     xfree(histHashTable);
719     histHashTable = NULL;
720 }
721 
722 /* Computes a new hash table size, when the current one is too small. */
723 static unsigned
getHashTableSize(int hlen)724 getHashTableSize(int hlen)
725 {
726     unsigned target = hlen * 2;
727     unsigned e = 5;
728     unsigned size;
729     while ((size = 1<<e) < target)
730 	e++;
731 #ifdef PRIME_LENGTH			/* need good HTL */
732     /* Not all prime, but most are and none have factors smaller than 11. */
733     return size+15;
734 #else
735     assert((size & (size-1)) == 0);	/* must be a power of two */
736     return size;
737 #endif
738 }
739 
740 /* Create the hash table or resize, if necessary. */
741 static void
createHistHashTable(int hlen)742 createHistHashTable(int hlen)
743 {
744     if (hlen == 0) {
745 	discardHistHashTable();
746         return;
747     }
748     if (hlen < 0) {
749 	if (histlen <= 0)
750 	    return;			/* no need for hash table */
751 	hlen = histlen;
752     }
753     if (histHashTable != NULL) {
754 	if (histCount < histHashTableLength * 3 / 4)
755 	    return;			/* good enough for now */
756 	discardHistHashTable();		/* too small */
757     }
758     histHashTableLength = getHashTableSize(
759 	hlen > (int)histCount ? hlen : (int)histCount);
760     histHashTable = xmalloc(histHashTableLength * sizeof(struct Hist *));
761     memset(histHashTable, 0, histHashTableLength * sizeof(struct Hist *));
762     assert(histHashTable[0] == emptyHTE);
763 
764     /* Now insert all the entries on the history list into the hash table. */
765     {
766         struct Hist *hp;
767         for (hp = &Histlist; (hp = hp->Hnext) != NULL;) {
768             unsigned lpHash = hashhist(&hp->Hlex);
769             assert(!hp->Hhash || hp->Hhash == lpHash);
770             hp->Hhash = 0;              /* force insert to new hash table */
771             insertHistHashTable(hp, lpHash);
772         }
773     }
774 }
775 
776 /* Insert np into the hash table.  We assume that np is already on the
777  * Histlist.  The specified hashval matches the new Hist entry but has not yet
778  * been assigned to Hhash (or the element is already on the hash table). */
779 static void
insertHistHashTable(struct Hist * np,unsigned hashval)780 insertHistHashTable(struct Hist *np, unsigned hashval)
781 {
782     unsigned rehashes = 0;
783     unsigned hi = 0;
784     if (!histHashTable)
785 	return;
786     if (np->Hhash != 0) {
787         /* already in hash table */
788         assert(hashval == np->Hhash);
789         return;
790     }
791     assert(np != deletedHTE);
792     /* Find a free (empty or deleted) slot, using linear rehash. */
793     assert(histHashTable);
794     for (rehashes = 0;
795          ((hi = hash2tableIndex(hashval + rehashes, histHashTableLength)),
796           histHashTable[hi] != emptyHTE && histHashTable[hi] != deletedHTE);
797          rehashes++) {
798         assert(np != histHashTable[hi]);
799         if (rehashes >= histHashTableLength / 10) {
800             /* Hash table is full, so grow it.  We assume the create function
801              * will roughly double the size we give it.  Create initializes the
802              * new table with everything on the Histlist, so we are done when
803              * it returns.  */
804 #ifdef DEBUG_HIST
805 	    xprintf("Growing history hash table from %d ...",
806 		histHashTableLength);
807 	    flush();
808 #endif
809             discardHistHashTable();
810             createHistHashTable(histHashTableLength);
811 #ifdef DEBUG_HIST
812 	    xprintf("to %d.\n", histHashTableLength);
813 #endif
814             return;
815         }
816     }
817     /* Might be sensible to grow hash table if rehashes is "too big" here. */
818     if (histHashTable[hi] == deletedHTE)
819 	hashStats.deleted--;
820     histHashTable[hi] = np;
821     np->Hhash = hashval;
822     hashStats.insertCount++;
823     hashStats.rehashes += rehashes;
824 }
825 
826 /* Remove the 'np' entry from the hash table. */
827 static void
removeHistHashTable(struct Hist * np)828 removeHistHashTable(struct Hist *np)
829 {
830     unsigned hi = np->Hhash;
831     if (!histHashTable || !hi)
832         return;                         /* no hash table or not on it */
833     /* find desired entry */
834     while ((hi = hash2tableIndex(hi, histHashTableLength)),
835            histHashTable[hi] != emptyHTE) {
836         if (np == histHashTable[hi]) {
837 	    unsigned i;
838 	    unsigned deletes = 0;
839 	    histHashTable[hi] = deletedHTE; /* dummy, but non-zero entry */
840 	    /* now peek ahead to see if the dummies are really necessary. */
841 	    i = 1;
842 	    while (histHashTable[hash2tableIndex(hi+i, histHashTableLength)] ==
843 		   deletedHTE)
844 		i++;
845 	    if (histHashTable[hash2tableIndex(hi+i, histHashTableLength)] ==
846 		emptyHTE) {
847 		/* dummies are no longer necessary placeholders. */
848 		deletes = i;
849 		while (i-- > 0) {
850 		    histHashTable[hash2tableIndex(hi+i, histHashTableLength)] =
851 			emptyHTE;
852 		}
853 	    }
854 	    hashStats.deleted += 1 - deletes; /* delta deleted entries */
855 	    hashStats.removeCount++;
856             return;
857         }
858         hi++;                           /* linear rehash */
859     }
860     assert(!"Hist entry not found in hash table");
861 }
862 
863 /* Search the history hash table for a command matching lp, using hashval as
864  * its hash value. */
865 static struct Hist *
findHistHashTable(struct wordent * lp,unsigned hashval)866 findHistHashTable(struct wordent *lp, unsigned hashval)
867 {
868     unsigned deleted = 0;		/* number of deleted entries skipped */
869     unsigned hi = hashval;
870     struct Hist *hp;
871     if (!histHashTable)
872 	return NULL;
873     while ((hi = hash2tableIndex(hi, histHashTableLength)),
874            (hp = histHashTable[hi]) != emptyHTE) {
875         if (hp == deletedHTE)
876 	    deleted++;
877 	else if (hp->Hhash == hashval && heq(lp, &(hp->Hlex)))
878             return hp;
879 	if (deleted > (histHashTableLength>>4)) {
880 	    /* lots of deletes, so we need a sparser table. */
881             discardHistHashTable();
882             createHistHashTable(histHashTableLength);
883 	    return findHistHashTable(lp, hashval);
884 	}
885         hi++;                           /* linear rehash */
886     }
887     return NULL;
888 }
889 
890 /* When merge semantics are in use, find the approximate predecessor for the
891  * new entry, so that the Htime entries are decreasing.  Return the entry just
892  * before the first entry with equal times, so the caller can check for
893  * duplicates.  When pTime is not NULL, use it as a starting point for search,
894  * otherwise search from beginning (largest time value) of history list. */
895 PG_STATIC struct Hist *
mergeInsertionPoint(struct Hist * np,struct Hist * pTime)896 mergeInsertionPoint(
897     struct Hist *np,                      /* new entry to be inserted */
898     struct Hist *pTime)                   /* hint about where to insert */
899 {
900     struct Hist *pp, *p;
901     if (histTail && histTail->Htime >= np->Htime)
902 	pTime = histTail;		/* new entry goes at the end */
903     if (histMerg && histMerg != &Histlist && histMerg != Histlist.Hnext) {
904 	/* Check above and below previous insertion point, in case we're adding
905 	 * sequential times in the middle of the list (e.g. history -M). */
906 	if (histMerg->Htime >= np->Htime)
907 	    pTime = histMerg;
908 	else if (histMerg->Hprev->Htime >= np->Htime)
909 	    pTime = histMerg->Hprev;
910     }
911     if (pTime) {
912         /* With hint, search up the list until Htime is greater.  We skip past
913          * the equal ones, too, so our caller can elide duplicates. */
914         pp = pTime;
915         while (pp != &Histlist && pp->Htime <= np->Htime)
916             pp = pp->Hprev;
917     } else
918         pp = &Histlist;
919     /* Search down the list while current entry's time is too large. */
920     while ((p = pp->Hnext) && (p->Htime > np->Htime))
921             pp = p;                     /* advance insertion point */
922     /* Remember recent position as hint for next time */
923     histMerg = pp;
924     return pp;
925 }
926 
927 /* Bubble Hnum & Href in new entry down to pp through earlier part of list. */
bubbleHnumHrefDown(struct Hist * np,struct Hist * pp)928 PG_STATIC void bubbleHnumHrefDown(struct Hist *np, struct Hist *pp)
929 {
930     struct Hist *p;
931     for (p = Histlist.Hnext; p != pp->Hnext; p = p->Hnext) {
932         /* swap Hnum & Href values of p and np. */
933         int n = p->Hnum, r = p->Href;
934         p->Hnum = np->Hnum; p->Href = np->Href;
935         np->Hnum = n; np->Href = r;
936     }
937 }
938 
939 /* Enter new command into the history list according to current settings. */
940 struct Hist *
enthist(int event,struct wordent * lp,int docopy,int mflg,int hlen)941 enthist(
942   int event,				/* newly incremented global eventno */
943   struct wordent *lp,
944   int docopy,
945   int mflg,				/* true if merge requested */
946   int hlen)				/* -1 if unknown */
947 {
948     struct Hist *p = NULL, *pp = &Histlist, *pTime = NULL;
949     struct Hist *np;
950     const Char *dp;
951     unsigned lpHash = 0;                /* non-zero if hashing entries */
952 
953     if ((dp = varval(STRhistdup)) != STRNULL) {
954 	if (eq(dp, STRerase)) {
955 	    /* masaoki@akebono.tky.hp.com (Kobayashi Masaoki) */
956             createHistHashTable(hlen);
957             lpHash = hashhist(lp);
958             assert(lpHash != 0);
959             p = findHistHashTable(lp, lpHash);
960             if (p) {
961 		if (Htime != 0 && p->Htime > Htime)
962 		    Htime = p->Htime;
963                 /* If we are merging, and the old entry is at the place we want
964                  * to insert the new entry, then remember the place. */
965                 if (mflg && Htime != 0 && p->Hprev->Htime >= Htime)
966                     pTime = p->Hprev;
967 		if (!fastMergeErase)
968 		    renumberHist(p);	/* Reset Href of subsequent entries */
969                 hremove(p);
970 		hfree(p);
971                 p = NULL;               /* so new entry is allocated below */
972 	    }
973 	}
974 	else if (eq(dp, STRall)) {
975             createHistHashTable(hlen);
976             lpHash = hashhist(lp);
977             assert(lpHash != 0);
978             p = findHistHashTable(lp, lpHash);
979 	    if (p)   /* p!=NULL, only update this entry's Htime below */
980 		eventno--;		/* not adding a new event */
981 	}
982 	else if (eq(dp, STRprev)) {
983 	    if (pp->Hnext && heq(lp, &(pp->Hnext->Hlex))) {
984 		p = pp->Hnext;
985 		eventno--;
986 	    }
987 	}
988     }
989 
990     np = p ? p : xmalloc(sizeof(*np));
991 
992     /* Pick up timestamp set by lex() in Htime if reading saved history */
993     if (Htime != 0) {
994 	np->Htime = Htime;
995 	Htime = 0;
996     }
997     else
998         (void) time(&(np->Htime));
999 
1000     if (p == np)
1001         return np;                      /* reused existing entry */
1002 
1003     /* Initialize the new entry. */
1004     np->Hnum = np->Href = event;
1005     if (docopy) {
1006         copylex(&np->Hlex, lp);
1007 	if (histvalid)
1008 	    np->histline = Strsave(histline.s);
1009 	else
1010 	    np->histline = NULL;
1011     }
1012     else {
1013 	np->Hlex.next = lp->next;
1014 	lp->next->prev = &np->Hlex;
1015 	np->Hlex.prev = lp->prev;
1016         lp->prev->next = &np->Hlex;
1017         np->histline = NULL;
1018     }
1019     np->Hhash = 0;
1020 
1021     /* The head of history list is the default insertion point.
1022        If merging, advance insertion point, in pp, according to Htime. */
1023     /* XXX -- In histdup=all, Htime values can be non-monotonic. */
1024     if (mflg) {                         /* merge according to np->Htime */
1025         pp = mergeInsertionPoint(np, pTime);
1026         for (p = pp->Hnext; p && p->Htime == np->Htime; pp = p, p = p->Hnext) {
1027             if (heq(&p->Hlex, &np->Hlex)) {
1028                 eventno--;              /* duplicate, so don't add new event */
1029                 hfree(np);
1030                 return (p);
1031               }
1032           }
1033         /* pp is now the last entry with time >= to np. */
1034 	if (!fastMergeErase) {		/* renumber at end of loadhist */
1035 	    /* Before inserting np after pp, bubble its Hnum & Href values down
1036 	     * through the earlier part of list. */
1037 	    bubbleHnumHrefDown(np, pp);
1038 	}
1039     }
1040     else
1041         pp = &Histlist;                 /* insert at beginning of history */
1042     hinsert(np, pp);
1043     if (lpHash && hlen != 0)		/* erase & all modes use hash table */
1044         insertHistHashTable(np, lpHash);
1045     else
1046         discardHistHashTable();
1047     return (np);
1048 }
1049 
1050 static void
hfree(struct Hist * hp)1051 hfree(struct Hist *hp)
1052 {
1053     assert(hp != histMerg);
1054     if (hp->Hhash)
1055         removeHistHashTable(hp);
1056     freelex(&hp->Hlex);
1057     if (hp->histline)
1058         xfree(hp->histline);
1059     xfree(hp);
1060 }
1061 
1062 PG_STATIC void
phist(struct Hist * hp,int hflg)1063 phist(struct Hist *hp, int hflg)
1064 {
1065     if (hp->Href < 0)
1066 	return;
1067     if (hflg & HIST_ONLY) {
1068 	int old_output_raw;
1069 
1070        /*
1071         * Control characters have to be written as is (output_raw).
1072         * This way one can preserve special characters (like tab) in
1073         * the history file.
1074         * From: mveksler@vnet.ibm.com (Veksler Michael)
1075         */
1076 	old_output_raw = output_raw;
1077         output_raw = 1;
1078 	cleanup_push(&old_output_raw, output_raw_restore);
1079 	if (hflg & HIST_TIME)
1080 	    /*
1081 	     * Make file entry with history time in format:
1082 	     * "+NNNNNNNNNN" (10 digits, left padded with ascii '0')
1083 	     */
1084 
1085 	    xprintf("#+%010lu\n", (unsigned long)hp->Htime);
1086 
1087 	if (HistLit && hp->histline)
1088 	    xprintf("%" TCSH_S "\n", hp->histline);
1089 	else
1090 	    prlex(&hp->Hlex);
1091         cleanup_until(&old_output_raw);
1092     }
1093     else {
1094 	Char   *cp = str2short("%h\t%T\t%R\n");
1095 	Char *p;
1096 	struct varent *vp = adrof(STRhistory);
1097 
1098 	if (vp && vp->vec != NULL && vp->vec[0] && vp->vec[1])
1099 	    cp = vp->vec[1];
1100 
1101 	p = tprintf(FMT_HISTORY, cp, NULL, hp->Htime, hp);
1102 	cleanup_push(p, xfree);
1103 	for (cp = p; *cp;)
1104 	    xputwchar(*cp++);
1105 	cleanup_until(p);
1106     }
1107 }
1108 
1109 PG_STATIC void
dophist(int n,int hflg)1110 dophist(int n, int hflg)
1111 {
1112     struct Hist *hp;
1113     if (setintr) {
1114 	int old_pintr_disabled;
1115 
1116 	pintr_push_enable(&old_pintr_disabled);
1117 	cleanup_until(&old_pintr_disabled);
1118     }
1119     if ((hflg & HIST_REV) == 0) {
1120 	/* Since the history list is stored most recent first, non-reversing
1121 	 * print needs to print (backwards) up the list. */
1122 	if ((unsigned)n >= histCount)
1123 	    hp = histTail;
1124 	else {
1125 	    for (hp = Histlist.Hnext;
1126 		 --n > 0 && hp->Hnext != NULL;
1127 		 hp = hp->Hnext)
1128 		;
1129 	}
1130 	if (hp == NULL)
1131 	    return;			/* nothing to print */
1132 	for (; hp != &Histlist; hp = hp->Hprev)
1133 	    phist(hp, hflg);
1134     } else {
1135 	for (hp = Histlist.Hnext; n-- > 0 && hp != NULL; hp = hp->Hnext)
1136 	    phist(hp, hflg);
1137     }
1138 }
1139 
1140 /*ARGSUSED*/
1141 void
dohist(Char ** vp,struct command * c)1142 dohist(Char **vp, struct command *c)
1143 {
1144     int     n, hflg = 0;
1145 
1146     USE(c);
1147     if (getn(varval(STRhistory)) == 0)
1148 	return;
1149     while (*++vp && **vp == '-') {
1150 	Char   *vp2 = *vp;
1151 
1152 	while (*++vp2)
1153 	    switch (*vp2) {
1154 	    case 'c':
1155 		hflg |= HIST_CLEAR;
1156 		break;
1157 	    case 'h':
1158 		hflg |= HIST_ONLY;
1159 		break;
1160 	    case 'r':
1161 		hflg |= HIST_REV;
1162 		break;
1163 	    case 'S':
1164 		hflg |= HIST_SAVE;
1165 		break;
1166 	    case 'L':
1167 		hflg |= HIST_LOAD;
1168 		break;
1169 	    case 'M':
1170 	    	hflg |= HIST_MERGE;
1171 		break;
1172 	    case 'T':
1173 	    	hflg |= HIST_TIME;
1174 		break;
1175 	    default:
1176 		stderror(ERR_HISTUS, "chrSLMT");
1177 		break;
1178 	    }
1179     }
1180     if (hflg & HIST_CLEAR) {
1181         struct Hist *np, *hp;
1182         for (hp = &Histlist; (np = hp->Hnext) != NULL;)
1183             hremove(np), hfree(np);
1184     }
1185 
1186     if (hflg & (HIST_LOAD | HIST_MERGE))
1187 	loadhist(*vp, (hflg & HIST_MERGE) ? 1 : 0);
1188     else if (hflg & HIST_SAVE)
1189 	rechist(*vp, 1);
1190     else {
1191 	if (*vp)
1192 	    n = getn(*vp);
1193 	else {
1194 	    n = getn(varval(STRhistory));
1195 	}
1196 	dophist(n, hflg);
1197     }
1198 }
1199 
1200 void
cleanhist(void)1201 cleanhist(void)
1202 {
1203     struct Hist *hp, *np;
1204 
1205     for (hp = &Histlist; (np = hp->Hnext) != NULL;) {
1206 	if (np->Hnum != HIST_PURGE)
1207 	    return;
1208 	hremove(np), hfree(np);
1209     }
1210 }
1211 
1212 char *
fmthist(int fmt,ptr_t ptr)1213 fmthist(int fmt, ptr_t ptr)
1214 {
1215     struct Hist *hp = ptr;
1216     char *buf;
1217 
1218     switch (fmt) {
1219     case 'h':
1220 	return xasprintf("%6d", hp->Hnum);
1221     case 'R':
1222 	if (HistLit && hp->histline)
1223 	    return xasprintf("%" TCSH_S, hp->histline);
1224 	else {
1225 	    Char *istr, *ip;
1226 	    char *p;
1227 
1228 	    istr = sprlex(&hp->Hlex);
1229 	    buf = xmalloc(Strlen(istr) * MB_LEN_MAX + 1);
1230 
1231 	    for (p = buf, ip = istr; *ip != '\0'; ip++)
1232 		p += one_wctomb(p, *ip);
1233 
1234 	    *p = '\0';
1235 	    xfree(istr);
1236 	    return buf;
1237 	}
1238     default:
1239 	buf = xmalloc(1);
1240 	buf[0] = '\0';
1241 	return buf;
1242     }
1243 }
1244 
1245 static void
dotlock_cleanup(void * lockpath)1246 dotlock_cleanup(void* lockpath)
1247 {
1248 	dot_unlock((char*)lockpath);
1249 }
1250 
1251 /* Save history before exiting the shell. */
1252 void
rechist(Char * xfname,int ref)1253 rechist(Char *xfname, int ref)
1254 {
1255     Char    *snum, *rs;
1256     int     fp, ftmp, oldidfds, ophup_disabled;
1257     struct varent *shist;
1258     char path[MAXPATHLEN];
1259     struct stat st;
1260     static Char *fname;
1261     static Char   *dumphist[] = {STRhistory, STRmhT, 0, 0};
1262 
1263     if (xfname == NULL && !ref)
1264 	return;
1265 
1266     fname = xfname;
1267     ophup_disabled = phup_disabled;
1268     phup_disabled = 1;
1269 
1270     /*
1271      * If $savehist is just set, we use the value of $history
1272      * else we use the value in $savehist
1273      */
1274     if (((snum = varval(STRsavehist)) == STRNULL) &&
1275 	((snum = varval(STRhistory)) == STRNULL))
1276 	snum = STRmaxint;
1277 
1278 
1279     if (fname == NULL) {
1280 	if ((fname = varval(STRhistfile)) == STRNULL)
1281 	    fname = Strspl(varval(STRhome), &STRtildothist[1]);
1282 	else
1283 	    fname = Strsave(fname);
1284     }
1285     else
1286 	fname = globone(fname, G_ERROR);
1287     cleanup_push(fname, xfree);
1288 
1289     /*
1290      * The 'savehist merge' feature is intended for an environment
1291      * with numerous shells being in simultaneous use. Imagine
1292      * any kind of window system. All these shells 'share' the same
1293      * ~/.history file for recording their command line history.
1294      * We try to handle the case of multiple shells trying to merge
1295      * histories at the same time, by creating semi-unique filenames
1296      * and saving the history there first and then trying to rename
1297      * them in the proper history file.
1298      *
1299      * Users that like to nuke their environment require here an atomic
1300      * loadhist-creat-dohist(dumphist)-close sequence which is given
1301 		 * by optional lock parameter to savehist.
1302      *
1303      * jw.
1304      */
1305     /*
1306      * We need the didfds stuff before loadhist otherwise
1307      * exec in a script will fail to print if merge is set.
1308      * From: mveksler@iil.intel.com (Veksler Michael)
1309      */
1310     oldidfds = didfds;
1311     didfds = 0;
1312     if ((shist = adrof(STRsavehist)) != NULL && shist->vec != NULL) {
1313 	size_t i;
1314 	int merge = 0, lock = 0;
1315 
1316 	for (i = 1; shist->vec[i]; i++) {
1317 	    if (eq(shist->vec[i], STRmerge))
1318 		merge++;
1319 	    if (eq(shist->vec[i], STRlock))
1320 		lock++;
1321 	}
1322 
1323 	if (merge) {
1324 	    jmp_buf_t osetexit;
1325 	    if (lock) {
1326 #ifndef WINNT_NATIVE
1327 		char *lockpath = strsave(short2str(fname));
1328 		cleanup_push(lockpath, xfree);
1329 		/* Poll in 100 miliseconds interval to obtain the lock. */
1330 		if ((dot_lock(lockpath, 100) == 0))
1331 		    cleanup_push(lockpath, dotlock_cleanup);
1332 #endif
1333 	    }
1334 	    getexit(osetexit);
1335 	    if (setexit() == 0)
1336 		loadhist(fname, 1);
1337 	    resexit(osetexit);
1338 	}
1339     }
1340     rs = randsuf();
1341     xsnprintf(path, sizeof(path), "%" TCSH_S ".%" TCSH_S, fname, rs);
1342     xfree(rs);
1343 
1344     fp = xcreat(path, 0600);
1345     if (fp == -1) {
1346 	didfds = oldidfds;
1347 	cleanup_until(fname);
1348 	phup_disabled = ophup_disabled;
1349 	return;
1350     }
1351     /* Try to preserve ownership and permissions of the original history file */
1352 #ifndef WINNT_NATIVE
1353     if (stat(short2str(fname), &st) != -1) {
1354 	TCSH_IGNORE(fchown(fp, st.st_uid, st.st_gid));
1355 	TCSH_IGNORE(fchmod(fp, st.st_mode));
1356     }
1357 #else
1358     UNREFERENCED_PARAMETER(st);
1359 #endif
1360     ftmp = SHOUT;
1361     SHOUT = fp;
1362     dumphist[2] = snum;
1363     dohist(dumphist, NULL);
1364     xclose(fp);
1365     SHOUT = ftmp;
1366     didfds = oldidfds;
1367 #ifndef WINNT_NATIVE
1368     (void)rename(path, short2str(fname));
1369 #else
1370     (void)ReplaceFile(short2str(fname), path, NULL, 0, NULL, NULL);
1371 #endif
1372     cleanup_until(fname);
1373     phup_disabled = ophup_disabled;
1374 }
1375 
1376 
1377 /* This is the entry point for loading history data from a file. */
1378 void
loadhist(Char * fname,int mflg)1379 loadhist(Char *fname, int mflg)
1380 {
1381     static Char   *loadhist_cmd[] = {STRsource, NULL, NULL, NULL};
1382     loadhist_cmd[1] = mflg ? STRmm : STRmh;
1383 
1384     if (fname != NULL)
1385 	loadhist_cmd[2] = fname;
1386     else if ((fname = varval(STRhistfile)) != STRNULL)
1387 	loadhist_cmd[2] = fname;
1388     else
1389 	loadhist_cmd[2] = STRtildothist;
1390 
1391     dosource(loadhist_cmd, NULL);
1392 
1393     /* During history merging (enthist sees mflg set), we disable management of
1394      * Hnum and Href (because fastMergeErase is true).  So now reset all the
1395      * values based on the final ordering of the history list. */
1396     if (mflg) {
1397 	int n = eventno;
1398         struct Hist *hp = &Histlist;
1399         while ((hp = hp->Hnext))
1400 	    hp->Hnum = hp->Href = n--;
1401     }
1402 }
1403 
1404 void
sethistory(int n)1405 sethistory(int n)
1406 {
1407     histlen = n;
1408     discardExcess(histlen);
1409 }
1410