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