On Unix, zebra/z'mbol uses automake.
[idzebra-moved-to-github.git] / index / recindex.c
1 /*
2  * Copyright (C) 1994-1999, Index Data
3  * All rights reserved.
4  * Sebastian Hammer, Adam Dickmeiss
5  *
6  * $Log: recindex.c,v $
7  * Revision 1.29  2000-04-05 09:49:35  adam
8  * On Unix, zebra/z'mbol uses automake.
9  *
10  * Revision 1.28  1999/12/08 22:44:45  adam
11  * Zebra/Z'mbol dependencies added.
12  *
13  * Revision 1.27  1999/10/29 10:02:33  adam
14  * Fixed decompression buffer overflow.
15  *
16  * Revision 1.26  1999/07/06 13:34:57  adam
17  * Fixed bug (introduced by previous commit).
18  *
19  * Revision 1.25  1999/07/06 12:28:04  adam
20  * Updated record index structure. Format includes version ID. Compression
21  * algorithm ID is stored for each record block.
22  *
23  * Revision 1.24  1999/06/25 13:48:02  adam
24  * Updated MSVC project files.
25  * Added BZIP2 record compression (not very well tested).
26  *
27  * Revision 1.23  1999/05/26 07:49:13  adam
28  * C++ compilation.
29  *
30  * Revision 1.22  1999/02/18 12:49:34  adam
31  * Changed file naming scheme for register files as well as record
32  * store/index files.
33  *
34  * Revision 1.21  1999/02/02 14:51:03  adam
35  * Updated WIN32 code specific sections. Changed header.
36  *
37  * Revision 1.20  1998/01/12 15:04:08  adam
38  * The test option (-s) only uses read-lock (and not write lock).
39  *
40  * Revision 1.19  1997/09/17 12:19:16  adam
41  * Zebra version corresponds to YAZ version 1.4.
42  * Changed Zebra server so that it doesn't depend on global common_resource.
43  *
44  * Revision 1.18  1997/07/15 16:28:42  adam
45  * Bug fix: storeData didn't work with files with multiple records.
46  * Bug fix: fixed memory management with records; not really well
47  *  thought through.
48  *
49  * Revision 1.17  1997/02/12 20:39:46  adam
50  * Implemented options -f <n> that limits the log to the first <n>
51  * records.
52  * Changed some log messages also.
53  *
54  * Revision 1.16  1996/06/04 10:19:00  adam
55  * Minor changes - removed include of ctype.h.
56  *
57  * Revision 1.15  1996/05/13  14:23:06  adam
58  * Work on compaction of set/use bytes in dictionary.
59  *
60  * Revision 1.14  1996/02/01  20:48:15  adam
61  * The total size of records are always checked in rec_cache_insert to
62  * reduce memory usage.
63  *
64  * Revision 1.13  1995/12/11  09:12:49  adam
65  * The rec_get function returns NULL if record doesn't exist - will
66  * happen in the server if the result set records have been deleted since
67  * the creation of the set (i.e. the search).
68  * The server saves a result temporarily if it is 'volatile', i.e. the
69  * set is register dependent.
70  *
71  * Revision 1.12  1995/12/07  17:38:47  adam
72  * Work locking mechanisms for concurrent updates/commit.
73  *
74  * Revision 1.11  1995/12/06  13:58:26  adam
75  * Improved flushing of records - all flushes except the last one
76  * don't write the last accessed. Also flush takes place if record
77  * info occupy more than about 256k.
78  *
79  * Revision 1.10  1995/12/06  12:41:24  adam
80  * New command 'stat' for the index program.
81  * Filenames can be read from stdin by specifying '-'.
82  * Bug fix/enhancement of the transformation from terms to regular
83  * expressons in the search engine.
84  *
85  * Revision 1.9  1995/11/30  08:34:33  adam
86  * Started work on commit facility.
87  * Changed a few malloc/free to xmalloc/xfree.
88  *
89  * Revision 1.8  1995/11/28  14:26:21  adam
90  * Bug fix: recordId with constant wasn't right.
91  * Bug fix: recordId dictionary entry wasn't deleted when needed.
92  *
93  * Revision 1.7  1995/11/28  09:09:43  adam
94  * Zebra config renamed.
95  * Use setting 'recordId' to identify record now.
96  * Bug fix in recindex.c: rec_release_blocks was invokeded even
97  * though the blocks were already released.
98  * File traversal properly deletes records when needed.
99  *
100  * Revision 1.6  1995/11/25  10:24:06  adam
101  * More record fields - they are enumerated now.
102  * New options: flagStoreData flagStoreKey.
103  *
104  * Revision 1.5  1995/11/22  17:19:18  adam
105  * Record management uses the bfile system.
106  *
107  * Revision 1.4  1995/11/20  16:59:46  adam
108  * New update method: the 'old' keys are saved for each records.
109  *
110  * Revision 1.3  1995/11/16  15:34:55  adam
111  * Uses new record management system in both indexer and server.
112  *
113  * Revision 1.2  1995/11/15  19:13:08  adam
114  * Work on record management.
115  *
116  * Revision 1.1  1995/11/15  14:46:20  adam
117  * Started work on better record management system.
118  *
119  */
120
121
122 /*
123  *  Format of first block
124  *      next       (4 bytes)
125  *      ref_count  (4 bytes)
126  *      block      (504 bytes)
127  *
128  *  Format of subsequent blocks 
129  *      next  (4 bytes)
130  *      block (508 bytes)
131  *
132  *  Format of each record
133  *      sysno
134  *      (length, data) - pairs
135  *      length = 0 if same as previous
136  */
137 #include <stdio.h>
138 #include <assert.h>
139 #include <string.h>
140
141 #include "recindxp.h"
142
143 #if HAVE_BZLIB_H
144 #include <bzlib.h>
145 #endif
146 static void rec_write_head (Records p)
147 {
148     int r;
149
150     assert (p);
151     assert (p->index_BFile);
152
153     r = bf_write (p->index_BFile, 0, 0, sizeof(p->head), &p->head);    
154     if (r)
155     {
156         logf (LOG_FATAL|LOG_ERRNO, "write head of %s", p->index_fname);
157         exit (1);
158     }
159 }
160
161 static void rec_tmp_expand (Records p, int size)
162 {
163     if (p->tmp_size < size + 2048 ||
164         p->tmp_size < p->head.block_size[REC_BLOCK_TYPES-1]*2)
165     {
166         xfree (p->tmp_buf);
167         p->tmp_size = size + p->head.block_size[REC_BLOCK_TYPES-1]*2 + 2048;
168         p->tmp_buf = (char *) xmalloc (p->tmp_size);
169     }
170 }
171
172 static int read_indx (Records p, int sysno, void *buf, int itemsize, 
173                       int ignoreError)
174 {
175     int r;
176     int pos = (sysno-1)*itemsize;
177
178     r = bf_read (p->index_BFile, 1+pos/128, pos%128, itemsize, buf);
179     if (r != 1 && !ignoreError)
180     {
181         logf (LOG_FATAL|LOG_ERRNO, "read in %s at pos %ld",
182               p->index_fname, (long) pos);
183         exit (1);
184     }
185     return r;
186 }
187
188 static void write_indx (Records p, int sysno, void *buf, int itemsize)
189 {
190     int pos = (sysno-1)*itemsize;
191
192     bf_write (p->index_BFile, 1+pos/128, pos%128, itemsize, buf);
193 }
194
195 static void rec_release_blocks (Records p, int sysno)
196 {
197     struct record_index_entry entry;
198     int freeblock;
199     char block_and_ref[sizeof(short) + sizeof(int)];
200     int dst_type;
201     int first = 1;
202
203     if (read_indx (p, sysno, &entry, sizeof(entry), 1) != 1)
204         return ;
205
206     freeblock = entry.next;
207     assert (freeblock > 0);
208     dst_type = freeblock & 7;
209     assert (dst_type < REC_BLOCK_TYPES);
210     freeblock = freeblock / 8;
211     while (freeblock)
212     {
213         if (bf_read (p->data_BFile[dst_type], freeblock, 0,
214                      sizeof(block_and_ref), block_and_ref) != 1)
215         {
216             logf (LOG_FATAL|LOG_ERRNO, "read in rec_del_single");
217             exit (1);
218         }
219         if (first)
220         {
221             short ref;
222             memcpy (&ref, block_and_ref + sizeof(int), sizeof(ref));
223             --ref;
224             memcpy (block_and_ref + sizeof(int), &ref, sizeof(ref));
225             if (ref)
226             {
227                 if (bf_write (p->data_BFile[dst_type], freeblock, 0,
228                               sizeof(block_and_ref), block_and_ref))
229                 {
230                     logf (LOG_FATAL|LOG_ERRNO, "write in rec_del_single");
231                     exit (1);
232                 }
233                 return;
234             }
235             first = 0;
236         }
237         
238         if (bf_write (p->data_BFile[dst_type], freeblock, 0, sizeof(freeblock),
239                       &p->head.block_free[dst_type]))
240         {
241             logf (LOG_FATAL|LOG_ERRNO, "write in rec_del_single");
242             exit (1);
243         }
244         p->head.block_free[dst_type] = freeblock;
245         memcpy (&freeblock, block_and_ref, sizeof(int));
246
247         p->head.block_used[dst_type]--;
248     }
249     p->head.total_bytes -= entry.size;
250 }
251
252 static void rec_delete_single (Records p, Record rec)
253 {
254     struct record_index_entry entry;
255
256     rec_release_blocks (p, rec->sysno);
257
258     entry.next = p->head.index_free;
259     entry.size = 0;
260     p->head.index_free = rec->sysno;
261     write_indx (p, rec->sysno, &entry, sizeof(entry));
262 }
263
264 static void rec_write_tmp_buf (Records p, int size, int *sysnos)
265 {
266     struct record_index_entry entry;
267     int no_written = 0;
268     char *cptr = p->tmp_buf;
269     int block_prev = -1, block_free;
270     int dst_type = 0;
271     int i;
272
273     for (i = 1; i<REC_BLOCK_TYPES; i++)
274         if (size >= p->head.block_move[i])
275             dst_type = i;
276     while (no_written < size)
277     {
278         block_free = p->head.block_free[dst_type];
279         if (block_free)
280         {
281             if (bf_read (p->data_BFile[dst_type],
282                          block_free, 0, sizeof(*p->head.block_free),
283                          &p->head.block_free[dst_type]) != 1)
284             {
285                 logf (LOG_FATAL|LOG_ERRNO, "read in %s at free block %d",
286                       p->data_fname[dst_type], block_free);
287                 exit (1);
288             }
289         }
290         else
291             block_free = p->head.block_last[dst_type]++;
292         if (block_prev == -1)
293         {
294             entry.next = block_free*8 + dst_type;
295             entry.size = size;
296             p->head.total_bytes += size;
297             while (*sysnos > 0)
298             {
299                 write_indx (p, *sysnos, &entry, sizeof(entry));
300                 sysnos++;
301             }
302         }
303         else
304         {
305             memcpy (cptr, &block_free, sizeof(int));
306             bf_write (p->data_BFile[dst_type], block_prev, 0, 0, cptr);
307             cptr = p->tmp_buf + no_written;
308         }
309         block_prev = block_free;
310         no_written += p->head.block_size[dst_type] - sizeof(int);
311         p->head.block_used[dst_type]++;
312     }
313     assert (block_prev != -1);
314     block_free = 0;
315     memcpy (cptr, &block_free, sizeof(int));
316     bf_write (p->data_BFile[dst_type], block_prev, 0,
317               sizeof(int) + (p->tmp_buf+size) - cptr, cptr);
318 }
319
320 Records rec_open (BFiles bfs, int rw, int compression_method)
321 {
322     Records p;
323     int i, r;
324     int version;
325
326     p = (Records) xmalloc (sizeof(*p));
327     p->compression_method = compression_method;
328     p->rw = rw;
329     p->tmp_size = 1024;
330     p->tmp_buf = (char *) xmalloc (p->tmp_size);
331     p->index_fname = "reci";
332     p->index_BFile = bf_open (bfs, p->index_fname, 128, rw);
333     if (p->index_BFile == NULL)
334     {
335         logf (LOG_FATAL|LOG_ERRNO, "open %s", p->index_fname);
336         exit (1);
337     }
338     r = bf_read (p->index_BFile, 0, 0, 0, p->tmp_buf);
339     switch (r)
340     {
341     case 0:
342         memcpy (p->head.magic, REC_HEAD_MAGIC, sizeof(p->head.magic));
343         sprintf (p->head.version, "%3d", REC_VERSION);
344         p->head.index_free = 0;
345         p->head.index_last = 1;
346         p->head.no_records = 0;
347         p->head.total_bytes = 0;
348         for (i = 0; i<REC_BLOCK_TYPES; i++)
349         {
350             p->head.block_free[i] = 0;
351             p->head.block_last[i] = 1;
352             p->head.block_used[i] = 0;
353         }
354         p->head.block_size[0] = 128;
355         p->head.block_move[0] = 0;
356         for (i = 1; i<REC_BLOCK_TYPES; i++)
357         {
358             p->head.block_size[i] = p->head.block_size[i-1] * 4;
359             p->head.block_move[i] = p->head.block_size[i] * 24;
360         }
361         if (rw)
362             rec_write_head (p);
363         break;
364     case 1:
365         memcpy (&p->head, p->tmp_buf, sizeof(p->head));
366         if (memcmp (p->head.magic, REC_HEAD_MAGIC, sizeof(p->head.magic)))
367         {
368             logf (LOG_FATAL, "file %s has bad format", p->index_fname);
369             exit (1);
370         }
371         version = atoi (p->head.version);
372         if (version != REC_VERSION)
373         {
374             logf (LOG_FATAL, "file %s is version %d, but version"
375                   " %d is required", p->index_fname, version, REC_VERSION);
376             exit (1);
377         }
378         break;
379     }
380     for (i = 0; i<REC_BLOCK_TYPES; i++)
381     {
382         char str[80];
383         sprintf (str, "recd%c", i + 'A');
384         p->data_fname[i] = (char *) xmalloc (strlen(str)+1);
385         strcpy (p->data_fname[i], str);
386         p->data_BFile[i] = NULL;
387     }
388     for (i = 0; i<REC_BLOCK_TYPES; i++)
389     {
390         if (!(p->data_BFile[i] = bf_open (bfs, p->data_fname[i],
391                                           p->head.block_size[i],
392                                           rw)))
393         {
394             logf (LOG_FATAL|LOG_ERRNO, "bf_open %s", p->data_fname[i]);
395             exit (1);
396         }
397     }
398     p->cache_max = 400;
399     p->cache_cur = 0;
400     p->record_cache = (struct record_cache_entry *)
401         xmalloc (sizeof(*p->record_cache)*p->cache_max);
402     zebra_mutex_init (&p->mutex);
403     return p;
404 }
405
406 static void rec_encode_unsigned (unsigned n, unsigned char *buf, int *len)
407 {
408     (*len) = 0;
409     while (n > 127)
410     {
411         buf[*len] = 128 + (n & 127);
412         n = n >> 7;
413         (*len)++;
414     }
415     buf[*len] = n;
416     (*len)++;
417 }
418
419 static void rec_decode_unsigned(unsigned *np, unsigned char *buf, int *len)
420 {
421     unsigned n = 0;
422     unsigned w = 1;
423     (*len) = 0;
424
425     while (buf[*len] > 127)
426     {
427         n += w*(buf[*len] & 127);
428         w = w << 7;
429         (*len)++;
430     }
431     n += w * buf[*len];
432     (*len)++;
433     *np = n;
434 }
435
436 static void rec_cache_flush_block1 (Records p, Record rec, Record last_rec,
437                                     char **out_buf, int *out_size,
438                                     int *out_offset)
439 {
440     int i;
441     int len;
442
443     for (i = 0; i<REC_NO_INFO; i++)
444     {
445         if (*out_offset + (int) rec->size[i] + 20 > *out_size)
446         {
447             int new_size = *out_offset + rec->size[i] + 65536;
448             char *np = (char *) xmalloc (new_size);
449             if (*out_offset)
450                 memcpy (np, *out_buf, *out_offset);
451             xfree (*out_buf);
452             *out_size = new_size;
453             *out_buf = np;
454         }
455         if (i == 0)
456         {
457             rec_encode_unsigned (rec->sysno, *out_buf + *out_offset, &len);
458             (*out_offset) += len;
459         }
460         if (rec->size[i] == 0)
461         {
462             rec_encode_unsigned (1, *out_buf + *out_offset, &len);
463             (*out_offset) += len;
464         }
465         else if (last_rec && rec->size[i] == last_rec->size[i] &&
466                  !memcmp (rec->info[i], last_rec->info[i], rec->size[i]))
467         {
468             rec_encode_unsigned (0, *out_buf + *out_offset, &len);
469             (*out_offset) += len;
470         }
471         else
472         {
473             rec_encode_unsigned (rec->size[i]+1, *out_buf + *out_offset, &len);
474             (*out_offset) += len;
475             memcpy (*out_buf + *out_offset, rec->info[i], rec->size[i]);
476             (*out_offset) += rec->size[i];
477         }
478     }
479 }
480
481 static void rec_write_multiple (Records p, int saveCount)
482 {
483     int i;
484     short ref_count = 0;
485     char compression_method;
486     Record last_rec = 0;
487     int out_size = 1000;
488     int out_offset = 0;
489     char *out_buf = (char *) xmalloc (out_size);
490     int *sysnos = (int *) xmalloc (sizeof(*sysnos) * (p->cache_cur + 1));
491     int *sysnop = sysnos;
492
493     for (i = 0; i<p->cache_cur - saveCount; i++)
494     {
495         struct record_cache_entry *e = p->record_cache + i;
496         switch (e->flag)
497         {
498         case recordFlagNew:
499             rec_cache_flush_block1 (p, e->rec, last_rec, &out_buf,
500                                     &out_size, &out_offset);
501             *sysnop++ = e->rec->sysno;
502             ref_count++;
503             e->flag = recordFlagNop;
504             last_rec = e->rec;
505             break;
506         case recordFlagWrite:
507             rec_release_blocks (p, e->rec->sysno);
508             rec_cache_flush_block1 (p, e->rec, last_rec, &out_buf,
509                                     &out_size, &out_offset);
510             *sysnop++ = e->rec->sysno;
511             ref_count++;
512             e->flag = recordFlagNop;
513             last_rec = e->rec;
514             break;
515         case recordFlagDelete:
516             rec_delete_single (p, e->rec);
517             e->flag = recordFlagNop;
518             break;
519         default:
520             break;
521         }
522     }
523
524     *sysnop = -1;
525     if (ref_count)
526     {
527         int csize = 0;  /* indicate compression "not performed yet" */
528         compression_method = p->compression_method;
529         switch (compression_method)
530         {
531         case REC_COMPRESS_BZIP2:
532 #if HAVE_BZLIB_H        
533             csize = out_offset + (out_offset >> 6) + 620;
534             rec_tmp_expand (p, csize);
535             i = bzBuffToBuffCompress (p->tmp_buf+sizeof(int)+sizeof(short)+
536                                       sizeof(char),
537                                       &csize, out_buf, out_offset, 1, 0, 30);
538             if (i != BZ_OK)
539             {
540                 logf (LOG_WARN, "bzBuffToBuffCompress error code=%d", i);
541                 csize = 0;
542             }
543             logf (LOG_LOG, "compress %4d %5d %5d", ref_count, out_offset,
544                   csize);
545 #endif
546             break;
547         case REC_COMPRESS_NONE:
548             break;
549         }
550         if (!csize)  
551         {
552             /* either no compression or compression not supported ... */
553             csize = out_offset;
554             rec_tmp_expand (p, csize);
555             memcpy (p->tmp_buf + sizeof(int) + sizeof(short) + sizeof(char),
556                     out_buf, out_offset);
557             csize = out_offset;
558             compression_method = REC_COMPRESS_NONE;
559         }
560         memcpy (p->tmp_buf + sizeof(int), &ref_count, sizeof(ref_count));
561         memcpy (p->tmp_buf + sizeof(int)+sizeof(short),
562                 &compression_method, sizeof(compression_method));
563                 
564         /* -------- compression */
565         rec_write_tmp_buf (p, csize + sizeof(short) + sizeof(char), sysnos);
566     }
567     xfree (out_buf);
568     xfree (sysnos);
569 }
570
571 static void rec_cache_flush (Records p, int saveCount)
572 {
573     int i, j;
574
575     if (saveCount >= p->cache_cur)
576         saveCount = 0;
577
578     rec_write_multiple (p, saveCount);
579
580     for (i = 0; i<p->cache_cur - saveCount; i++)
581     {
582         struct record_cache_entry *e = p->record_cache + i;
583         rec_rm (&e->rec);
584     } 
585     /* i still being used ... */
586     for (j = 0; j<saveCount; j++, i++)
587         memcpy (p->record_cache+j, p->record_cache+i,
588                 sizeof(*p->record_cache));
589     p->cache_cur = saveCount;
590 }
591
592 static Record *rec_cache_lookup (Records p, int sysno,
593                                  enum recordCacheFlag flag)
594 {
595     int i;
596     for (i = 0; i<p->cache_cur; i++)
597     {
598         struct record_cache_entry *e = p->record_cache + i;
599         if (e->rec->sysno == sysno)
600         {
601             if (flag != recordFlagNop && e->flag == recordFlagNop)
602                 e->flag = flag;
603             return &e->rec;
604         }
605     }
606     return NULL;
607 }
608
609 static void rec_cache_insert (Records p, Record rec, enum recordCacheFlag flag)
610 {
611     struct record_cache_entry *e;
612
613     if (p->cache_cur == p->cache_max)
614         rec_cache_flush (p, 1);
615     else if (p->cache_cur > 0)
616     {
617         int i, j;
618         int used = 0;
619         for (i = 0; i<p->cache_cur; i++)
620         {
621             Record r = (p->record_cache + i)->rec;
622             for (j = 0; j<REC_NO_INFO; j++)
623                 used += r->size[j];
624         }
625         if (used > 90000)
626             rec_cache_flush (p, 1);
627     }
628     assert (p->cache_cur < p->cache_max);
629
630     e = p->record_cache + (p->cache_cur)++;
631     e->flag = flag;
632     e->rec = rec_cp (rec);
633 }
634
635 void rec_close (Records *pp)
636 {
637     Records p = *pp;
638     int i;
639
640     assert (p);
641
642     zebra_mutex_destroy (&p->mutex);
643     rec_cache_flush (p, 0);
644     xfree (p->record_cache);
645
646     if (p->rw)
647         rec_write_head (p);
648
649     if (p->index_BFile)
650         bf_close (p->index_BFile);
651
652     for (i = 0; i<REC_BLOCK_TYPES; i++)
653     {
654         if (p->data_BFile[i])
655             bf_close (p->data_BFile[i]);
656         xfree (p->data_fname[i]);
657     }
658     xfree (p->tmp_buf);
659     xfree (p);
660     *pp = NULL;
661 }
662
663 static Record rec_get_int (Records p, int sysno)
664 {
665     int i, in_size, r;
666     Record rec, *recp;
667     struct record_index_entry entry;
668     int freeblock, dst_type;
669     char *nptr, *cptr;
670     char *in_buf = 0;
671     char *bz_buf = 0;
672     int bz_size;
673     char compression_method;
674
675     assert (sysno > 0);
676     assert (p);
677
678     if ((recp = rec_cache_lookup (p, sysno, recordFlagNop)))
679         return rec_cp (*recp);
680
681     if (read_indx (p, sysno, &entry, sizeof(entry), 1) < 1)
682         return NULL;       /* record is not there! */
683
684     if (!entry.size)
685         return NULL;       /* record is deleted */
686
687     dst_type = entry.next & 7;
688     assert (dst_type < REC_BLOCK_TYPES);
689     freeblock = entry.next / 8;
690
691     assert (freeblock > 0);
692     
693     rec_tmp_expand (p, entry.size);
694
695     cptr = p->tmp_buf;
696     r = bf_read (p->data_BFile[dst_type], freeblock, 0, 0, cptr);
697     if (r < 0)
698         return 0;
699     memcpy (&freeblock, cptr, sizeof(freeblock));
700
701     while (freeblock)
702     {
703         int tmp;
704
705         cptr += p->head.block_size[dst_type] - sizeof(freeblock);
706         
707         memcpy (&tmp, cptr, sizeof(tmp));
708         r = bf_read (p->data_BFile[dst_type], freeblock, 0, 0, cptr);
709         if (r < 0)
710             return 0;
711         memcpy (&freeblock, cptr, sizeof(freeblock));
712         memcpy (cptr, &tmp, sizeof(tmp));
713     }
714
715     rec = (Record) xmalloc (sizeof(*rec));
716     rec->sysno = sysno;
717     memcpy (&compression_method, p->tmp_buf + sizeof(int) + sizeof(short),
718             sizeof(compression_method));
719     in_buf = p->tmp_buf + sizeof(int) + sizeof(short) + sizeof(char);
720     in_size = entry.size - sizeof(short) - sizeof(char);
721     switch (compression_method)
722     {
723     case REC_COMPRESS_BZIP2:
724 #if HAVE_BZLIB_H
725         bz_size = entry.size * 20 + 100;
726         while (1)
727         {
728             bz_buf = (char *) xmalloc (bz_size);
729             i = bzBuffToBuffDecompress (bz_buf, &bz_size, in_buf, in_size, 0, 0);
730             logf (LOG_LOG, "decompress %5d %5d", in_size, bz_size);
731             if (i == BZ_OK)
732                 break;
733             logf (LOG_LOG, "failed");
734             xfree (bz_buf);
735             bz_size *= 2;
736         }
737         in_buf = bz_buf;
738         in_size = bz_size;
739 #else
740         logf (LOG_FATAL, "cannot decompress record(s) in BZIP2 format");
741         exit (1);
742 #endif
743         break;
744     case REC_COMPRESS_NONE:
745         break;
746     }
747     for (i = 0; i<REC_NO_INFO; i++)
748         rec->info[i] = 0;
749
750     nptr = in_buf;                /* skip ref count */
751     while (nptr < in_buf + in_size)
752     {
753         int this_sysno;
754         int len;
755         rec_decode_unsigned (&this_sysno, nptr, &len);
756         nptr += len;
757
758         for (i = 0; i < REC_NO_INFO; i++)
759         {
760             int this_size;
761             rec_decode_unsigned (&this_size, nptr, &len);
762             nptr += len;
763
764             if (this_size == 0)
765                 continue;
766             rec->size[i] = this_size-1;
767
768             if (rec->size[i])
769             {
770                 rec->info[i] = nptr;
771                 nptr += rec->size[i];
772             }
773             else
774                 rec->info[i] = NULL;
775         }
776         if (this_sysno == sysno)
777             break;
778     }
779     for (i = 0; i<REC_NO_INFO; i++)
780     {
781         if (rec->info[i] && rec->size[i])
782         {
783             char *np = xmalloc (rec->size[i]);
784             memcpy (np, rec->info[i], rec->size[i]);
785             rec->info[i] = np;
786         }
787         else
788         {
789             assert (rec->info[i] == 0);
790             assert (rec->size[i] == 0);
791         }
792     }
793     xfree (bz_buf);
794     rec_cache_insert (p, rec, recordFlagNop);
795     return rec;
796 }
797
798 Record rec_get (Records p, int sysno)
799 {
800     Record rec;
801     zebra_mutex_lock (&p->mutex);
802
803     rec = rec_get_int (p, sysno);
804     zebra_mutex_unlock (&p->mutex);
805     return rec;
806 }
807
808 static Record rec_new_int (Records p)
809 {
810     int sysno, i;
811     Record rec;
812
813     assert (p);
814     rec = (Record) xmalloc (sizeof(*rec));
815     if (1 || p->head.index_free == 0)
816         sysno = (p->head.index_last)++;
817     else
818     {
819         struct record_index_entry entry;
820
821         read_indx (p, p->head.index_free, &entry, sizeof(entry), 0);
822         sysno = p->head.index_free;
823         p->head.index_free = entry.next;
824     }
825 #if ZMBOL
826 #else
827     if (sysno > 100000)
828     {
829         logf (LOG_FATAL, "100,000 record limit reached");
830         exit (1);
831     }
832 #endif
833     (p->head.no_records)++;
834     rec->sysno = sysno;
835     for (i = 0; i < REC_NO_INFO; i++)
836     {
837         rec->info[i] = NULL;
838         rec->size[i] = 0;
839     }
840     rec_cache_insert (p, rec, recordFlagNew);
841     return rec;
842 }
843
844 Record rec_new (Records p)
845 {
846     Record rec;
847     zebra_mutex_lock (&p->mutex);
848
849     rec = rec_new_int (p);
850     zebra_mutex_unlock (&p->mutex);
851     return rec;
852 }
853
854 void rec_del (Records p, Record *recpp)
855 {
856     Record *recp;
857
858     zebra_mutex_lock (&p->mutex);
859     (p->head.no_records)--;
860     if ((recp = rec_cache_lookup (p, (*recpp)->sysno, recordFlagDelete)))
861     {
862         rec_rm (recp);
863         *recp = *recpp;
864     }
865     else
866     {
867         rec_cache_insert (p, *recpp, recordFlagDelete);
868         rec_rm (recpp);
869     }
870     zebra_mutex_unlock (&p->mutex);
871     *recpp = NULL;
872 }
873
874 void rec_put (Records p, Record *recpp)
875 {
876     Record *recp;
877
878     zebra_mutex_lock (&p->mutex);
879     if ((recp = rec_cache_lookup (p, (*recpp)->sysno, recordFlagWrite)))
880     {
881         rec_rm (recp);
882         *recp = *recpp;
883     }
884     else
885     {
886         rec_cache_insert (p, *recpp, recordFlagWrite);
887         rec_rm (recpp);
888     }
889     zebra_mutex_unlock (&p->mutex);
890     *recpp = NULL;
891 }
892
893 void rec_rm (Record *recpp)
894 {
895     int i;
896
897     if (!*recpp)
898         return ;
899     for (i = 0; i < REC_NO_INFO; i++)
900         xfree ((*recpp)->info[i]);
901     xfree (*recpp);
902     *recpp = NULL;
903 }
904
905 Record rec_cp (Record rec)
906 {
907     Record n;
908     int i;
909
910     n = (Record) xmalloc (sizeof(*n));
911     n->sysno = rec->sysno;
912     for (i = 0; i < REC_NO_INFO; i++)
913         if (!rec->info[i])
914         {
915             n->info[i] = NULL;
916             n->size[i] = 0;
917         }
918         else
919         {
920             n->size[i] = rec->size[i];
921             n->info[i] = (char *) xmalloc (rec->size[i]);
922             memcpy (n->info[i], rec->info[i], rec->size[i]);
923         }
924     return n;
925 }
926
927
928 char *rec_strdup (const char *s, size_t *len)
929 {
930     char *p;
931
932     if (!s)
933     {
934         *len = 0;
935         return NULL;
936     }
937     *len = strlen(s)+1;
938     p = (char *) xmalloc (*len);
939     strcpy (p, s);
940     return p;
941 }
942