Work on new API. Locking system re-implemented
[idzebra-moved-to-github.git] / index / kinput.c
1 /*
2  * Copyright (C) 1994-2002, Index Data
3  * All rights reserved.
4  * Sebastian Hammer, Adam Dickmeiss, Heikki Levanto
5  *
6  * $Id: kinput.c,v 1.45 2002-02-20 17:30:01 adam Exp $
7  *
8  * Bugs
9  *  - Allocates a lot of memory for the merge process, but never releases it.
10  *    Doesn't matter, as the program terminates soon after.  
11  
12  */
13  
14 #include <fcntl.h>
15 #ifdef WIN32
16 #include <io.h>
17 #else
18 #include <unistd.h>
19 #endif
20 #include <stdlib.h>
21 #include <string.h>
22 #include <stdio.h>
23 #include <assert.h>
24
25 #include "index.h"
26 #include "zserver.h"
27
28 #define KEY_SIZE (1+sizeof(struct it_key))
29 #define INP_NAME_MAX 768
30 #define INP_BUF_START 60000
31 #define INP_BUF_ADD  400000
32
33 static int no_diffs   = 0;
34 static int no_updates = 0;
35 static int no_deletions = 0;
36 static int no_insertions = 0;
37 static int no_iterations = 0;
38
39 struct key_file {
40     int   no;            /* file no */
41     off_t offset;        /* file offset */
42     unsigned char *buf;  /* buffer block */
43     size_t buf_size;     /* number of read bytes in block */
44     size_t chunk;        /* number of bytes allocated */
45     size_t buf_ptr;      /* current position in buffer */
46     char *prev_name;     /* last word read */
47     int   sysno;         /* last sysno */
48     int   seqno;         /* last seqno */
49     off_t length;        /* length of file */
50                          /* handler invoked in each read */
51     void (*readHandler)(struct key_file *keyp, void *rinfo);
52     void *readInfo;
53     Res res;
54 };
55
56 void getFnameTmp (Res res, char *fname, int no)
57 {
58     const char *pre;
59     
60     pre = res_get_def (res, "keyTmpDir", ".");
61     sprintf (fname, "%s/key%d.tmp", pre, no);
62 }
63
64 void extract_get_fname_tmp (ZebraHandle zh, char *fname, int no)
65 {
66     const char *pre;
67     
68     pre = res_get_def (zh->service->res, "keyTmpDir", ".");
69     sprintf (fname, "%s/key%d.tmp", pre, no);
70 }
71
72 void key_file_chunk_read (struct key_file *f)
73 {
74     int nr = 0, r = 0, fd;
75     char fname[1024];
76     getFnameTmp (f->res, fname, f->no);
77     fd = open (fname, O_BINARY|O_RDONLY);
78
79     f->buf_ptr = 0;
80     f->buf_size = 0;
81     if (fd == -1)
82     {
83         logf (LOG_WARN|LOG_ERRNO, "cannot open %s", fname);
84         return ;
85     }
86     if (!f->length)
87     {
88         if ((f->length = lseek (fd, 0L, SEEK_END)) == (off_t) -1)
89         {
90             logf (LOG_WARN|LOG_ERRNO, "cannot seek %s", fname);
91             close (fd);
92             return ;
93         }
94     }
95     if (lseek (fd, f->offset, SEEK_SET) == -1)
96     {
97         logf (LOG_WARN|LOG_ERRNO, "cannot seek %s", fname);
98         close(fd);
99         return ;
100     }
101     while (f->chunk - nr > 0)
102     {
103         r = read (fd, f->buf + nr, f->chunk - nr);
104         if (r <= 0)
105             break;
106         nr += r;
107     }
108     if (r == -1)
109     {
110         logf (LOG_WARN|LOG_ERRNO, "read of %s", fname);
111         close (fd);
112         return;
113     }
114     f->buf_size = nr;
115     if (f->readHandler)
116         (*f->readHandler)(f, f->readInfo);
117     close (fd);
118 }
119
120 struct key_file *key_file_init (int no, int chunk, Res res)
121 {
122     struct key_file *f;
123
124     f = (struct key_file *) xmalloc (sizeof(*f));
125     f->res = res;
126     f->sysno = 0;
127     f->seqno = 0;
128     f->no = no;
129     f->chunk = chunk;
130     f->offset = 0;
131     f->length = 0;
132     f->readHandler = NULL;
133     f->buf = (unsigned char *) xmalloc (f->chunk);
134     f->prev_name = (char *) xmalloc (INP_NAME_MAX);
135     *f->prev_name = '\0';
136     key_file_chunk_read (f);
137     return f;
138 }
139
140 int key_file_getc (struct key_file *f)
141 {
142     if (f->buf_ptr < f->buf_size)
143         return f->buf[(f->buf_ptr)++];
144     if (f->buf_size < f->chunk)
145         return EOF;
146     f->offset += f->buf_size;
147     key_file_chunk_read (f);
148     if (f->buf_ptr < f->buf_size)
149         return f->buf[(f->buf_ptr)++];
150     else
151         return EOF;
152 }
153
154 int key_file_decode (struct key_file *f)
155 {
156     int c, d;
157
158     c = key_file_getc (f);
159     switch (c & 192) 
160     {
161     case 0:
162         d = c;
163         break;
164     case 64:
165         d = ((c&63) << 8) + (key_file_getc (f) & 0xff);
166         break;
167     case 128:
168         d = ((c&63) << 8) + (key_file_getc (f) & 0xff);
169         d = (d << 8) + (key_file_getc (f) & 0xff);
170         break;
171     case 192:
172         d = ((c&63) << 8) + (key_file_getc (f) & 0xff);
173         d = (d << 8) + (key_file_getc (f) & 0xff);
174         d = (d << 8) + (key_file_getc (f) & 0xff);
175         break;
176     }
177     return d;
178 }
179
180 int key_file_read (struct key_file *f, char *key)
181 {
182     int i, d, c;
183     struct it_key itkey;
184
185     c = key_file_getc (f);
186     if (c == 0)
187     {
188         strcpy (key, f->prev_name);
189         i = 1+strlen (key);
190     }
191     else if (c == EOF)
192         return 0;
193     else
194     {
195         i = 0;
196         key[i++] = c;
197         while ((key[i++] = key_file_getc (f)))
198             ;
199         strcpy (f->prev_name, key);
200         f->sysno = 0;
201     }
202     d = key_file_decode (f);
203     key[i++] = d & 1;
204     d = d >> 1;
205     itkey.sysno = d + f->sysno;
206     if (d) 
207     {
208         f->sysno = itkey.sysno;
209         f->seqno = 0;
210     }
211     d = key_file_decode (f);
212     itkey.seqno = d + f->seqno;
213     f->seqno = itkey.seqno;
214     memcpy (key + i, &itkey, sizeof(struct it_key));
215     return i + sizeof (struct it_key);
216 }
217
218 struct heap_info {
219     struct {
220         struct key_file **file;
221         char   **buf;
222     } info;
223     int    heapnum;
224     int    *ptr;
225     int    (*cmp)(const void *p1, const void *p2);
226     Dict dict;
227     ISAMS isams;
228 #if ZMBOL
229     ISAM isam;
230     ISAMC isamc;
231     ISAMD isamd;
232 #endif
233 };
234
235 struct heap_info *key_heap_init (int nkeys,
236                                  int (*cmp)(const void *p1, const void *p2))
237 {
238     struct heap_info *hi;
239     int i;
240
241     hi = (struct heap_info *) xmalloc (sizeof(*hi));
242     hi->info.file = (struct key_file **)
243         xmalloc (sizeof(*hi->info.file) * (1+nkeys));
244     hi->info.buf = (char **) xmalloc (sizeof(*hi->info.buf) * (1+nkeys));
245     hi->heapnum = 0;
246     hi->ptr = (int *) xmalloc (sizeof(*hi->ptr) * (1+nkeys));
247     hi->cmp = cmp;
248     for (i = 0; i<= nkeys; i++)
249     {
250         hi->ptr[i] = i;
251         hi->info.buf[i] = (char *) xmalloc (INP_NAME_MAX);
252     }
253     return hi;
254 }
255
256 static void key_heap_swap (struct heap_info *hi, int i1, int i2)
257 {
258     int swap;
259
260     swap = hi->ptr[i1];
261     hi->ptr[i1] = hi->ptr[i2];
262     hi->ptr[i2] = swap;
263 }
264
265
266 static void key_heap_delete (struct heap_info *hi)
267 {
268     int cur = 1, child = 2;
269
270     assert (hi->heapnum > 0);
271
272     key_heap_swap (hi, 1, hi->heapnum);
273     hi->heapnum--;
274     while (child <= hi->heapnum) {
275         if (child < hi->heapnum &&
276             (*hi->cmp)(&hi->info.buf[hi->ptr[child]],
277                        &hi->info.buf[hi->ptr[child+1]]) > 0)
278             child++;
279         if ((*hi->cmp)(&hi->info.buf[hi->ptr[cur]],
280                        &hi->info.buf[hi->ptr[child]]) > 0)
281         {            
282             key_heap_swap (hi, cur, child);
283             cur = child;
284             child = 2*cur;
285         }
286         else
287             break;
288     }
289 }
290
291 static void key_heap_insert (struct heap_info *hi, const char *buf, int nbytes,
292                              struct key_file *kf)
293 {
294     int cur, parent;
295
296     cur = ++(hi->heapnum);
297     memcpy (hi->info.buf[hi->ptr[cur]], buf, nbytes);
298     hi->info.file[hi->ptr[cur]] = kf;
299
300     parent = cur/2;
301     while (parent && (*hi->cmp)(&hi->info.buf[hi->ptr[parent]],
302                                 &hi->info.buf[hi->ptr[cur]]) > 0)
303     {
304         key_heap_swap (hi, cur, parent);
305         cur = parent;
306         parent = cur/2;
307     }
308 }
309
310 static int heap_read_one (struct heap_info *hi, char *name, char *key)
311 {
312     int n, r;
313     char rbuf[INP_NAME_MAX];
314     struct key_file *kf;
315
316     if (!hi->heapnum)
317         return 0;
318     n = hi->ptr[1];
319     strcpy (name, hi->info.buf[n]);
320     kf = hi->info.file[n];
321     r = strlen(name);
322     memcpy (key, hi->info.buf[n] + r+1, KEY_SIZE);
323     key_heap_delete (hi);
324     if ((r = key_file_read (kf, rbuf)))
325         key_heap_insert (hi, rbuf, r, kf);
326     no_iterations++;
327     return 1;
328 }
329
330 struct heap_cread_info {
331     char prev_name[INP_NAME_MAX];
332     char cur_name[INP_NAME_MAX];
333     char *key;
334     struct heap_info *hi;
335     int mode;
336     int more;
337 };
338       
339 int heap_cread_item (void *vp, char **dst, int *insertMode)
340 {
341     struct heap_cread_info *p = (struct heap_cread_info *) vp;
342     struct heap_info *hi = p->hi;
343
344     if (p->mode == 1)
345     {
346         *insertMode = p->key[0];
347         memcpy (*dst, p->key+1, sizeof(struct it_key));
348         (*dst) += sizeof(struct it_key);
349         p->mode = 2;
350         return 1;
351     }
352     strcpy (p->prev_name, p->cur_name);
353     if (!(p->more = heap_read_one (hi, p->cur_name, p->key)))
354         return 0;
355     if (*p->cur_name && strcmp (p->cur_name, p->prev_name))
356     {
357         p->mode = 1;
358         return 0;
359     }
360     *insertMode = p->key[0];
361     memcpy (*dst, p->key+1, sizeof(struct it_key));
362     (*dst) += sizeof(struct it_key);
363     return 1;
364 }
365
366 #if ZMBOL
367 int heap_inpc (struct heap_info *hi)
368 {
369     struct heap_cread_info hci;
370     ISAMC_I isamc_i = (ISAMC_I) xmalloc (sizeof(*isamc_i));
371
372     hci.key = (char *) xmalloc (KEY_SIZE);
373     hci.mode = 1;
374     hci.hi = hi;
375     hci.more = heap_read_one (hi, hci.cur_name, hci.key);
376
377     isamc_i->clientData = &hci;
378     isamc_i->read_item = heap_cread_item;
379
380     while (hci.more)
381     {
382         char this_name[INP_NAME_MAX];
383         ISAMC_P isamc_p, isamc_p2;
384         char *dict_info;
385
386         strcpy (this_name, hci.cur_name);
387         assert (hci.cur_name[1]);
388         no_diffs++;
389         if ((dict_info = dict_lookup (hi->dict, hci.cur_name)))
390         {
391             memcpy (&isamc_p, dict_info+1, sizeof(ISAMC_P));
392             isamc_p2 = isc_merge (hi->isamc, isamc_p, isamc_i);
393             if (!isamc_p2)
394             {
395                 no_deletions++;
396                 if (!dict_delete (hi->dict, this_name))
397                     abort();
398             }
399             else 
400             {
401                 no_updates++;
402                 if (isamc_p2 != isamc_p)
403                     dict_insert (hi->dict, this_name,
404                                  sizeof(ISAMC_P), &isamc_p2);
405             }
406         } 
407         else
408         {
409             isamc_p = isc_merge (hi->isamc, 0, isamc_i);
410             no_insertions++;
411             dict_insert (hi->dict, this_name, sizeof(ISAMC_P), &isamc_p);
412         }
413     }
414     xfree (isamc_i);
415     xfree (hci.key);
416     return 0;
417
418
419 int heap_inpd (struct heap_info *hi)
420 {
421     struct heap_cread_info hci;
422     ISAMD_I isamd_i = (ISAMD_I) xmalloc (sizeof(*isamd_i));
423
424     hci.key = (char *) xmalloc (KEY_SIZE);
425     hci.mode = 1;
426     hci.hi = hi;
427     hci.more = heap_read_one (hi, hci.cur_name, hci.key);
428
429     isamd_i->clientData = &hci;
430     isamd_i->read_item = heap_cread_item;
431
432     while (hci.more)
433     {
434         char this_name[INP_NAME_MAX];
435         ISAMD_P isamd_p, isamd_p2;
436         char *dict_info;
437
438         strcpy (this_name, hci.cur_name);
439         assert (hci.cur_name[1]);
440         no_diffs++;
441         if ((dict_info = dict_lookup (hi->dict, hci.cur_name)))
442         {
443             memcpy (&isamd_p, dict_info+1, sizeof(ISAMD_P));
444             isamd_p2 = isamd_append (hi->isamd, isamd_p, isamd_i);
445             if (!isamd_p2)
446             {
447                 no_deletions++;
448                 if (!dict_delete (hi->dict, this_name))
449                     abort();
450             }
451             else 
452             {
453                 no_updates++;
454                 if (isamd_p2 != isamd_p)
455                     dict_insert (hi->dict, this_name,
456                                  sizeof(ISAMD_P), &isamd_p2);
457             }
458         } 
459         else
460         {
461             isamd_p = isamd_append (hi->isamd, 0, isamd_i);
462             no_insertions++;
463             dict_insert (hi->dict, this_name, sizeof(ISAMD_P), &isamd_p);
464         }
465     }
466     xfree (isamd_i);
467     return 0;
468
469
470 int heap_inp (struct heap_info *hi)
471 {
472     char *info;
473     char next_name[INP_NAME_MAX];
474     char cur_name[INP_NAME_MAX];
475     int key_buf_size = INP_BUF_START;
476     int key_buf_ptr;
477     char *next_key;
478     char *key_buf;
479     int more;
480     
481     next_key = (char *) xmalloc (KEY_SIZE);
482     key_buf = (char *) xmalloc (key_buf_size);
483     more = heap_read_one (hi, cur_name, key_buf);
484     while (more)                   /* EOF ? */
485     {
486         int nmemb;
487         key_buf_ptr = KEY_SIZE;
488         while (1)
489         {
490             if (!(more = heap_read_one (hi, next_name, next_key)))
491                 break;
492             if (*next_name && strcmp (next_name, cur_name))
493                 break;
494             memcpy (key_buf + key_buf_ptr, next_key, KEY_SIZE);
495             key_buf_ptr += KEY_SIZE;
496             if (key_buf_ptr+(int) KEY_SIZE >= key_buf_size)
497             {
498                 char *new_key_buf;
499                 new_key_buf = (char *) xmalloc (key_buf_size + INP_BUF_ADD);
500                 memcpy (new_key_buf, key_buf, key_buf_size);
501                 key_buf_size += INP_BUF_ADD;
502                 xfree (key_buf);
503                 key_buf = new_key_buf;
504             }
505         }
506         no_diffs++;
507         nmemb = key_buf_ptr / KEY_SIZE;
508         assert (nmemb * (int) KEY_SIZE == key_buf_ptr);
509         if ((info = dict_lookup (hi->dict, cur_name)))
510         {
511             ISAM_P isam_p, isam_p2;
512             memcpy (&isam_p, info+1, sizeof(ISAM_P));
513             isam_p2 = is_merge (hi->isam, isam_p, nmemb, key_buf);
514             if (!isam_p2)
515             {
516                 no_deletions++;
517                 if (!dict_delete (hi->dict, cur_name))
518                     abort ();
519             }
520             else 
521             {
522                 no_updates++;
523                 if (isam_p2 != isam_p)
524                     dict_insert (hi->dict, cur_name, sizeof(ISAM_P), &isam_p2);
525             }
526         }
527         else
528         {
529             ISAM_P isam_p;
530             no_insertions++;
531             isam_p = is_merge (hi->isam, 0, nmemb, key_buf);
532             dict_insert (hi->dict, cur_name, sizeof(ISAM_P), &isam_p);
533         }
534         memcpy (key_buf, next_key, KEY_SIZE);
535         strcpy (cur_name, next_name);
536     }
537     return 0;
538 }
539
540 #endif
541
542 int heap_inps (struct heap_info *hi)
543 {
544     struct heap_cread_info hci;
545     ISAMS_I isams_i = (ISAMS_I) xmalloc (sizeof(*isams_i));
546
547     hci.key = (char *) xmalloc (KEY_SIZE);
548     hci.mode = 1;
549     hci.hi = hi;
550     hci.more = heap_read_one (hi, hci.cur_name, hci.key);
551
552     isams_i->clientData = &hci;
553     isams_i->read_item = heap_cread_item;
554
555     while (hci.more)
556     {
557         char this_name[INP_NAME_MAX];
558         ISAMS_P isams_p;
559         char *dict_info;
560
561         strcpy (this_name, hci.cur_name);
562         assert (hci.cur_name[1]);
563         no_diffs++;
564         if (!(dict_info = dict_lookup (hi->dict, hci.cur_name)))
565         {
566             isams_p = isams_merge (hi->isams, isams_i);
567             no_insertions++;
568             dict_insert (hi->dict, this_name, sizeof(ISAMS_P), &isams_p);
569         }
570         else
571         {
572             logf (LOG_FATAL, "isams doesn't support this kind of update");
573             break;
574         }
575     }
576     xfree (isams_i);
577     return 0;
578
579
580 struct progressInfo {
581     time_t   startTime;
582     time_t   lastTime;
583     off_t    totalBytes;
584     off_t    totalOffset;
585 };
586
587 void progressFunc (struct key_file *keyp, void *info)
588 {
589     struct progressInfo *p = (struct progressInfo *) info;
590     time_t now, remaining;
591
592     if (keyp->buf_size <= 0 || p->totalBytes <= 0)
593         return ;
594     time (&now);
595
596     if (now >= p->lastTime+10)
597     {
598         p->lastTime = now;
599         remaining = (time_t) ((now - p->startTime)*
600             ((double) p->totalBytes/p->totalOffset - 1.0));
601         if (remaining <= 130)
602             logf (LOG_LOG, "Merge %2.1f%% completed; %ld seconds remaining",
603                  (100.0*p->totalOffset) / p->totalBytes, (long) remaining);
604         else
605             logf (LOG_LOG, "Merge %2.1f%% completed; %ld minutes remaining",
606                  (100.0*p->totalOffset) / p->totalBytes, (long) remaining/60);
607     }
608     p->totalOffset += keyp->buf_size;
609 }
610
611 #ifndef R_OK
612 #define R_OK 4
613 #endif
614
615 void zebra_index_merge (ZebraHandle zh)
616 {
617     struct key_file **kf;
618     char rbuf[1024];
619     int i, r;
620     struct heap_info *hi;
621     struct progressInfo progressInfo;
622     int nkeys = zh->key_file_no;
623     
624     if (nkeys < 0)
625     {
626         char fname[1024];
627         nkeys = 0;
628         while (1)
629         {
630             extract_get_fname_tmp  (zh, fname, nkeys+1);
631             if (access (fname, R_OK) == -1)
632                 break;
633             nkeys++;
634         }
635         if (!nkeys)
636             return ;
637     }
638     kf = (struct key_file **) xmalloc ((1+nkeys) * sizeof(*kf));
639     progressInfo.totalBytes = 0;
640     progressInfo.totalOffset = 0;
641     time (&progressInfo.startTime);
642     time (&progressInfo.lastTime);
643     for (i = 1; i<=nkeys; i++)
644     {
645         kf[i] = key_file_init (i, 8192, zh->service->res);
646         kf[i]->readHandler = progressFunc;
647         kf[i]->readInfo = &progressInfo;
648         progressInfo.totalBytes += kf[i]->length;
649         progressInfo.totalOffset += kf[i]->buf_size;
650     }
651     hi = key_heap_init (nkeys, key_qsort_compare);
652     hi->dict = zh->service->dict;
653     hi->isams = zh->service->isams;
654 #if ZMBOL
655     hi->isam = zh->service->isam;
656     hi->isamc = zh->service->isamc;
657     hi->isamd = zh->service->isamd;
658 #endif
659     
660     for (i = 1; i<=nkeys; i++)
661         if ((r = key_file_read (kf[i], rbuf)))
662             key_heap_insert (hi, rbuf, r, kf[i]);
663     if (zh->service->isams)
664         heap_inps (hi);
665 #if ZMBOL
666     else if (zh->service->isamc)
667         heap_inpc (hi);
668     else if (zh->service->isam)
669         heap_inp (hi);
670     else if (zh->service->isamd)
671         heap_inpd (hi);
672 #endif
673         
674     for (i = 1; i<=nkeys; i++)
675     {
676         extract_get_fname_tmp  (zh, rbuf, i);
677         unlink (rbuf);
678     }
679     logf (LOG_LOG, "Iterations . . .%7d", no_iterations);
680     logf (LOG_LOG, "Distinct words .%7d", no_diffs);
681     logf (LOG_LOG, "Updates. . . . .%7d", no_updates);
682     logf (LOG_LOG, "Deletions. . . .%7d", no_deletions);
683     logf (LOG_LOG, "Insertions . . .%7d", no_insertions);
684     zh->key_file_no = 0;
685 }
686
687 void key_input (ZebraHandle zh, int nkeys, int cache, Res res)
688                 
689 {
690     struct key_file **kf;
691     char rbuf[1024];
692     int i, r;
693     struct heap_info *hi;
694     struct progressInfo progressInfo;
695
696     if (nkeys < 0)
697     {
698         char fname[1024];
699         nkeys = 0;
700         while (1)
701         {
702             getFnameTmp (res, fname, nkeys+1);
703             if (access (fname, R_OK) == -1)
704                 break;
705             nkeys++;
706         }
707         if (!nkeys)
708             return ;
709     }
710     kf = (struct key_file **) xmalloc ((1+nkeys) * sizeof(*kf));
711     progressInfo.totalBytes = 0;
712     progressInfo.totalOffset = 0;
713     time (&progressInfo.startTime);
714     time (&progressInfo.lastTime);
715     for (i = 1; i<=nkeys; i++)
716     {
717         kf[i] = key_file_init (i, 8192, res);
718         kf[i]->readHandler = progressFunc;
719         kf[i]->readInfo = &progressInfo;
720         progressInfo.totalBytes += kf[i]->length;
721         progressInfo.totalOffset += kf[i]->buf_size;
722     }
723     hi = key_heap_init (nkeys, key_qsort_compare);
724     hi->dict = zh->service->dict;
725     hi->isams = zh->service->isams;
726 #if ZMBOL
727     hi->isam = zh->service->isam;
728     hi->isamc = zh->service->isamc;
729     hi->isamd = zh->service->isamd;
730 #endif
731     
732     for (i = 1; i<=nkeys; i++)
733         if ((r = key_file_read (kf[i], rbuf)))
734             key_heap_insert (hi, rbuf, r, kf[i]);
735     if (hi->isams)
736         heap_inps (hi);
737 #if ZMBOL
738     else if (hi->isamc)
739         heap_inpc (hi);
740     else if (hi->isam)
741         heap_inp (hi);
742     else if (hi->isamd)
743         heap_inpd (hi);
744 #endif
745         
746     for (i = 1; i<=nkeys; i++)
747     {
748         getFnameTmp (res, rbuf, i);
749         unlink (rbuf);
750     }
751     logf (LOG_LOG, "Iterations . . .%7d", no_iterations);
752     logf (LOG_LOG, "Distinct words .%7d", no_diffs);
753     logf (LOG_LOG, "Updates. . . . .%7d", no_updates);
754     logf (LOG_LOG, "Deletions. . . .%7d", no_deletions);
755     logf (LOG_LOG, "Insertions . . .%7d", no_insertions);
756
757     /* xmalloc_trav("unfreed"); while hunting leaks */     
758 }
759