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