xref: /dragonfly/contrib/tcsh-6/sh.hist.c (revision 84d884bf08edef6c02f15218458cd5df8010b654)
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