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