542b0966a403b3e43bacd63977cc94dd34fb1722
[idzebra-moved-to-github.git] / bfile / cfile.c
1 /*
2  * Copyright (C) 1995-1999, Index Data ApS
3  * All rights reserved.
4  * Sebastian Hammer, Adam Dickmeiss
5  *
6  * $Log: cfile.c,v $
7  * Revision 1.24  1999-05-12 13:08:06  adam
8  * First version of ISAMS.
9  *
10  * Revision 1.23  1998/10/15 13:09:29  adam
11  * Minor changes.
12  *
13  * Revision 1.22  1998/10/13 20:07:22  adam
14  * Changed some log messages.
15  *
16  * Revision 1.21  1998/08/24 17:29:52  adam
17  * Minor changes.
18  *
19  * Revision 1.20  1998/08/07 15:07:13  adam
20  * Fixed but in cf_commit_flat.
21  *
22  * Revision 1.19  1997/02/12 20:37:17  adam
23  * Changed the messages logged. No real code changed.
24  *
25  * Revision 1.18  1996/10/29 13:56:15  adam
26  * Include of zebrautl.h instead of alexutil.h.
27  *
28  * Revision 1.17  1996/04/19 16:49:00  adam
29  * Minor changes.
30  *
31  * Revision 1.16  1996/04/19  16:23:47  adam
32  * Serious bug fix in shadow implementation; function new_bucket might
33  * set wrong bucket number on new bucket.
34  *
35  * Revision 1.15  1996/04/18  16:02:56  adam
36  * Changed logging a bit.
37  * Removed warning message when commiting flat shadow files.
38  *
39  * Revision 1.14  1996/04/12  07:01:55  adam
40  * Yet another bug fix (next_block was initialized to 0; now set to 1).
41  *
42  * Revision 1.13  1996/04/09 14:48:49  adam
43  * Bug fix: offset calculation when using flat files was completely broken.
44  *
45  * Revision 1.12  1996/04/09  06:47:28  adam
46  * Function scan_areadef doesn't use sscanf (%n fails on this Linux).
47  *
48  * Revision 1.11  1996/03/26 15:59:05  adam
49  * The directory of the shadow table file can be specified by the new
50  * bf_lockDir call.
51  *
52  * Revision 1.10  1996/02/07  14:03:46  adam
53  * Work on flat indexed shadow files.
54  *
55  * Revision 1.9  1996/02/07  10:08:43  adam
56  * Work on flat shadow (not finished yet).
57  *
58  * Revision 1.8  1995/12/15  12:36:52  adam
59  * Moved hash file information to union.
60  * Renamed commit files.
61  *
62  * Revision 1.7  1995/12/15  10:35:07  adam
63  * Changed names of commit files.
64  *
65  * Revision 1.6  1995/12/11  09:03:53  adam
66  * New function: cf_unlink.
67  * New member of commit file head: state (0) deleted, (1) hash file.
68  *
69  * Revision 1.5  1995/12/08  16:21:14  adam
70  * Work on commit/update.
71  *
72  * Revision 1.4  1995/12/01  16:24:28  adam
73  * Commit files use separate meta file area.
74  *
75  * Revision 1.3  1995/12/01  11:37:22  adam
76  * Cached/commit files implemented as meta-files.
77  *
78  * Revision 1.2  1995/11/30  17:00:49  adam
79  * Several bug fixes. Commit system runs now.
80  *
81  * Revision 1.1  1995/11/30  08:33:11  adam
82  * Started work on commit facility.
83  *
84  */
85
86 #include <assert.h>
87 #include <stdlib.h>
88 #include <string.h>
89
90 #include <zebrautl.h>
91 #include <mfile.h>
92 #include "cfile.h"
93
94 static int write_head (CFile cf)
95 {
96     int left = cf->head.hash_size * sizeof(int);
97     int bno = 1;
98     const char *tab = (char*) cf->array;
99
100     if (!tab)
101         return 0;
102     while (left >= HASH_BSIZE)
103     {
104         mf_write (cf->hash_mf, bno++, 0, 0, tab);
105         tab += HASH_BSIZE;
106         left -= HASH_BSIZE;
107     }
108     if (left > 0)
109         mf_write (cf->hash_mf, bno, 0, left, tab);
110     return 0;
111 }
112
113 static int read_head (CFile cf)
114 {
115     int left = cf->head.hash_size * sizeof(int);
116     int bno = 1;
117     char *tab = (char*) cf->array;
118
119     if (!tab)
120         return 0;
121     while (left >= HASH_BSIZE)
122     {
123         mf_read (cf->hash_mf, bno++, 0, 0, tab);
124         tab += HASH_BSIZE;
125         left -= HASH_BSIZE;
126     }
127     if (left > 0)
128         mf_read (cf->hash_mf, bno, 0, left, tab);
129     return 0;
130 }
131
132
133 CFile cf_open (MFile mf, MFile_area area, const char *fname,
134                int block_size, int wflag, int *firstp)
135 {
136     char path[1024];
137     int i;
138     CFile cf = (CFile) xmalloc (sizeof(*cf));
139     int hash_bytes;
140    
141     cf->rmf = mf; 
142     logf (LOG_DEBUG, "cf: open %s %s", cf->rmf->name, wflag ? "rdwr" : "rd");
143     sprintf (path, "%s-b", fname);
144     if (!(cf->block_mf = mf_open (area, path, block_size, wflag)))
145     {
146         logf (LOG_FATAL|LOG_ERRNO, "Failed to open %s", path);
147         exit (1);
148     }
149     sprintf (path, "%s-i", fname);
150     if (!(cf->hash_mf = mf_open (area, path, HASH_BSIZE, wflag)))
151     {
152         logf (LOG_FATAL|LOG_ERRNO, "Failed to open %s", path);
153         exit (1);
154     }
155     assert (firstp);
156     if (!mf_read (cf->hash_mf, 0, 0, sizeof(cf->head), &cf->head) ||
157         !cf->head.state)
158     {
159         *firstp = 1;
160         cf->head.state = 1;
161         cf->head.block_size = block_size;
162         cf->head.hash_size = 199;
163         hash_bytes = cf->head.hash_size * sizeof(int);
164         cf->head.flat_bucket = cf->head.next_bucket = cf->head.first_bucket = 
165             (hash_bytes+sizeof(cf->head))/HASH_BSIZE + 2;
166         cf->head.next_block = 1;
167         if (wflag)
168             mf_write (cf->hash_mf, 0, 0, sizeof(cf->head), &cf->head);
169         cf->array = xmalloc (hash_bytes);
170         for (i = 0; i<cf->head.hash_size; i++)
171             cf->array[i] = 0;
172         if (wflag)
173             write_head (cf);
174     }
175     else
176     {
177         *firstp = 0;
178         assert (cf->head.block_size == block_size);
179         assert (cf->head.hash_size > 2);
180         hash_bytes = cf->head.hash_size * sizeof(int);
181         assert (cf->head.next_bucket > 0);
182         assert (cf->head.next_block > 0);
183         if (cf->head.state == 1)
184             cf->array = xmalloc (hash_bytes);
185         else
186             cf->array = NULL;
187         read_head (cf);
188     }
189     if (cf->head.state == 1)
190     {
191         cf->parray = xmalloc (cf->head.hash_size * sizeof(*cf->parray));
192         for (i = 0; i<cf->head.hash_size; i++)
193             cf->parray[i] = NULL;
194     }
195     else
196         cf->parray = NULL;
197     cf->bucket_lru_front = cf->bucket_lru_back = NULL;
198     cf->bucket_in_memory = 0;
199     cf->max_bucket_in_memory = 100;
200     cf->dirty = 0;
201     cf->iobuf = xmalloc (cf->head.block_size);
202     memset (cf->iobuf, 0, cf->head.block_size);
203     cf->no_hits = 0;
204     cf->no_miss = 0;
205     return cf;
206 }
207
208 static int cf_hash (CFile cf, int no)
209 {
210     return (no>>3) % cf->head.hash_size;
211 }
212
213 static void release_bucket (CFile cf, struct CFile_hash_bucket *p)
214 {
215     if (p->lru_prev)
216         p->lru_prev->lru_next = p->lru_next;
217     else
218         cf->bucket_lru_back = p->lru_next;
219     if (p->lru_next)
220         p->lru_next->lru_prev = p->lru_prev;
221     else
222         cf->bucket_lru_front = p->lru_prev;
223
224     *p->h_prev = p->h_next;
225     if (p->h_next)
226         p->h_next->h_prev = p->h_prev;
227     
228     --(cf->bucket_in_memory);
229     xfree (p);
230 }
231
232 static void flush_bucket (CFile cf, int no_to_flush)
233 {
234     int i;
235     struct CFile_hash_bucket *p;
236
237     for (i = 0; i != no_to_flush; i++)
238     {
239         p = cf->bucket_lru_back;
240         if (!p)
241             break;
242         if (p->dirty)
243         {
244             mf_write (cf->hash_mf, p->ph.this_bucket, 0, 0, &p->ph);
245             cf->dirty = 1;
246         }
247         release_bucket (cf, p);
248     }
249 }
250
251 static struct CFile_hash_bucket *alloc_bucket (CFile cf, int block_no, int hno)
252 {
253     struct CFile_hash_bucket *p, **pp;
254
255     if (cf->bucket_in_memory == cf->max_bucket_in_memory)
256         flush_bucket (cf, 1);
257     assert (cf->bucket_in_memory < cf->max_bucket_in_memory);
258     ++(cf->bucket_in_memory);
259     p = xmalloc (sizeof(*p));
260
261     p->lru_next = NULL;
262     p->lru_prev = cf->bucket_lru_front;
263     if (cf->bucket_lru_front)
264         cf->bucket_lru_front->lru_next = p;
265     else
266         cf->bucket_lru_back = p;
267     cf->bucket_lru_front = p; 
268
269     pp = cf->parray + hno;
270     p->h_next = *pp;
271     p->h_prev = pp;
272     if (*pp)
273         (*pp)->h_prev = &p->h_next;
274     *pp = p;
275     return p;
276 }
277
278 static struct CFile_hash_bucket *get_bucket (CFile cf, int block_no, int hno)
279 {
280     struct CFile_hash_bucket *p;
281
282     p = alloc_bucket (cf, block_no, hno);
283     if (!mf_read (cf->hash_mf, block_no, 0, 0, &p->ph))
284     {
285         logf (LOG_FATAL|LOG_ERRNO, "read get_bucket");
286         exit (1);
287     }
288     assert (p->ph.this_bucket == block_no);
289     p->dirty = 0;
290     return p;
291 }
292
293 static struct CFile_hash_bucket *new_bucket (CFile cf, int *block_nop, int hno)
294 {
295     struct CFile_hash_bucket *p;
296     int i, block_no;
297
298     block_no = *block_nop = cf->head.next_bucket++;
299     p = alloc_bucket (cf, block_no, hno);
300
301     for (i = 0; i<HASH_BUCKET; i++)
302     {
303         p->ph.vno[i] = 0;
304         p->ph.no[i] = 0;
305     }
306     p->ph.next_bucket = 0;
307     p->ph.this_bucket = block_no;
308     p->dirty = 1;
309     return p;
310 }
311
312 static int cf_lookup_flat (CFile cf, int no)
313 {
314     int hno = (no*sizeof(int))/HASH_BSIZE;
315     int off = (no*sizeof(int)) - hno*HASH_BSIZE;
316     int vno = 0;
317
318     mf_read (cf->hash_mf, hno+cf->head.next_bucket, off, sizeof(int), &vno);
319     return vno;
320 }
321
322 static int cf_lookup_hash (CFile cf, int no)
323 {
324     int hno = cf_hash (cf, no);
325     struct CFile_hash_bucket *hb;
326     int block_no, i;
327
328     for (hb = cf->parray[hno]; hb; hb = hb->h_next)
329     {
330         for (i = 0; i<HASH_BUCKET && hb->ph.vno[i]; i++)
331             if (hb->ph.no[i] == no)
332             {
333                 (cf->no_hits)++;
334                 return hb->ph.vno[i];
335             }
336     }
337     for (block_no = cf->array[hno]; block_no; block_no = hb->ph.next_bucket)
338     {
339         for (hb = cf->parray[hno]; hb; hb = hb->h_next)
340         {
341             if (hb->ph.this_bucket == block_no)
342                 break;
343         }
344         if (hb)
345             continue;
346 #if 0
347         /* extra check ... */
348         for (hb = cf->bucket_lru_back; hb; hb = hb->lru_next)
349         {
350             if (hb->ph.this_bucket == block_no)
351             {
352                 logf (LOG_FATAL, "Found hash bucket on other chain (1)");
353                 abort ();
354             }
355             for (i = 0; i<HASH_BUCKET && hb->ph.vno[i]; i++)
356                 if (hb->ph.no[i] == no)
357                 {
358                     logf (LOG_FATAL, "Found hash bucket on other chain (2)");
359                     abort ();
360                 }
361         }
362 #endif
363         (cf->no_miss)++;
364         hb = get_bucket (cf, block_no, hno);
365         for (i = 0; i<HASH_BUCKET && hb->ph.vno[i]; i++)
366             if (hb->ph.no[i] == no)
367                 return hb->ph.vno[i];
368     }
369     return 0;
370 }
371
372 static void cf_write_flat (CFile cf, int no, int vno)
373 {
374     int hno = (no*sizeof(int))/HASH_BSIZE;
375     int off = (no*sizeof(int)) - hno*HASH_BSIZE;
376
377     hno += cf->head.next_bucket;
378     if (hno >= cf->head.flat_bucket)
379         cf->head.flat_bucket = hno+1;
380     cf->dirty = 1;
381     mf_write (cf->hash_mf, hno, off, sizeof(int), &vno);
382 }
383
384 static void cf_moveto_flat (CFile cf)
385 {
386     struct CFile_hash_bucket *p;
387     int i, j;
388
389     logf (LOG_DEBUG, "cf: Moving to flat shadow: %s", cf->rmf->name);
390     logf (LOG_DEBUG, "cf: hits=%d miss=%d bucket_in_memory=%d total=%d",
391         cf->no_hits, cf->no_miss, cf->bucket_in_memory, 
392         cf->head.next_bucket - cf->head.first_bucket);
393     assert (cf->head.state == 1);
394     flush_bucket (cf, -1);
395     assert (cf->bucket_in_memory == 0);
396     p = xmalloc (sizeof(*p));
397     for (i = cf->head.first_bucket; i < cf->head.next_bucket; i++)
398     {
399         if (!mf_read (cf->hash_mf, i, 0, 0, &p->ph))
400         {
401             logf (LOG_FATAL|LOG_ERRNO, "read bucket moveto flat");
402             exit (1);
403         }
404         for (j = 0; j < HASH_BUCKET && p->ph.vno[j]; j++)
405             cf_write_flat (cf, p->ph.no[j], p->ph.vno[j]);
406     }
407     xfree (p);
408     xfree (cf->array);
409     cf->array = NULL;
410     xfree (cf->parray);
411     cf->parray = NULL;
412     cf->head.state = 2;
413     cf->dirty = 1;
414 }
415
416 static int cf_lookup (CFile cf, int no)
417 {
418     if (cf->head.state > 1)
419         return cf_lookup_flat (cf, no);
420     return cf_lookup_hash (cf, no);
421 }
422
423 static int cf_new_flat (CFile cf, int no)
424 {
425     int vno = (cf->head.next_block)++;
426
427     cf_write_flat (cf, no, vno);
428     return vno;
429 }
430
431 static int cf_new_hash (CFile cf, int no)
432 {
433     int hno = cf_hash (cf, no);
434     struct CFile_hash_bucket *hbprev = NULL, *hb = cf->parray[hno];
435     int *bucketpp = &cf->array[hno]; 
436     int i, vno = (cf->head.next_block)++;
437   
438     for (hb = cf->parray[hno]; hb; hb = hb->h_next)
439         if (!hb->ph.vno[HASH_BUCKET-1])
440             for (i = 0; i<HASH_BUCKET; i++)
441                 if (!hb->ph.vno[i])
442                 {
443                     (cf->no_hits)++;
444                     hb->ph.no[i] = no;
445                     hb->ph.vno[i] = vno;
446                     hb->dirty = 1;
447                     return vno;
448                 }
449
450     while (*bucketpp)
451     {
452         for (hb = cf->parray[hno]; hb; hb = hb->h_next)
453             if (hb->ph.this_bucket == *bucketpp)
454             {
455                 bucketpp = &hb->ph.next_bucket;
456                 hbprev = hb;
457                 break;
458             }
459         if (hb)
460             continue;
461
462 #if 0
463         /* extra check ... */
464         for (hb = cf->bucket_lru_back; hb; hb = hb->lru_next)
465         {
466             if (hb->ph.this_bucket == *bucketpp)
467             {
468                 logf (LOG_FATAL, "Found hash bucket on other chain");
469                 abort ();
470             }
471         }
472 #endif
473         (cf->no_miss)++;
474         hb = get_bucket (cf, *bucketpp, hno);
475         assert (hb);
476         for (i = 0; i<HASH_BUCKET; i++)
477             if (!hb->ph.vno[i])
478             {
479                 hb->ph.no[i] = no;
480                 hb->ph.vno[i] = vno;
481                 hb->dirty = 1;
482                 return vno;
483             }
484         bucketpp = &hb->ph.next_bucket;
485         hbprev = hb;
486     }
487     if (hbprev)
488         hbprev->dirty = 1;
489     hb = new_bucket (cf, bucketpp, hno);
490     hb->ph.no[0] = no;
491     hb->ph.vno[0] = vno;
492     return vno;
493 }
494
495 int cf_new (CFile cf, int no)
496 {
497     if (cf->head.state > 1)
498         return cf_new_flat (cf, no);
499     if (cf->no_miss*2 > cf->no_hits)
500     {
501         cf_moveto_flat (cf);
502         assert (cf->head.state > 1);
503         return cf_new_flat (cf, no);
504     }
505     return cf_new_hash (cf, no);
506 }
507
508
509 int cf_read (CFile cf, int no, int offset, int nbytes, void *buf)
510 {
511     int block;
512     
513     assert (cf);
514     if (!(block = cf_lookup (cf, no)))
515         return -1;
516     if (!mf_read (cf->block_mf, block, offset, nbytes, buf))
517     {
518         logf (LOG_FATAL|LOG_ERRNO, "cf_read no=%d, block=%d", no, block);
519         exit (1);
520     }
521     return 1;
522 }
523
524 int cf_write (CFile cf, int no, int offset, int nbytes, const void *buf)
525 {
526     int block;
527
528     assert (cf);
529     if (!(block = cf_lookup (cf, no)))
530     {
531         block = cf_new (cf, no);
532         if (offset || nbytes)
533         {
534             mf_read (cf->rmf, no, 0, 0, cf->iobuf);
535             memcpy (cf->iobuf + offset, buf, nbytes);
536             buf = cf->iobuf;
537             offset = 0;
538             nbytes = 0;
539         }
540     }
541     if (mf_write (cf->block_mf, block, offset, nbytes, buf))
542     {
543         logf (LOG_FATAL|LOG_ERRNO, "cf_write no=%d, block=%d", no, block);
544         exit (1);
545     }
546     return 0;
547 }
548
549 int cf_close (CFile cf)
550 {
551     logf (LOG_DEBUG, "cf: close hits=%d miss=%d bucket_in_memory=%d total=%d",
552           cf->no_hits, cf->no_miss, cf->bucket_in_memory,
553           cf->head.next_bucket - cf->head.first_bucket);
554     flush_bucket (cf, -1);
555     if (cf->dirty)
556     {
557         mf_write (cf->hash_mf, 0, 0, sizeof(cf->head), &cf->head);
558         write_head (cf);
559     }
560     mf_close (cf->hash_mf);
561     mf_close (cf->block_mf);
562     xfree (cf->array);
563     xfree (cf->parray);
564     xfree (cf->iobuf);
565     xfree (cf);
566     return 0;
567 }
568