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