f84d395a1bd32d702af2afcc22171a293b95cb80
[idzebra-moved-to-github.git] / bfile / cfile.c
1 /* $Id: cfile.c,v 1.38 2006-11-08 22:08:27 adam Exp $
2    Copyright (C) 1995-2006
3    Index Data ApS
4
5 This file is part of the Zebra server.
6
7 Zebra is free software; you can redistribute it and/or modify it under
8 the terms of the GNU General Public License as published by the Free
9 Software Foundation; either version 2, or (at your option) any later
10 version.
11
12 Zebra is distributed in the hope that it will be useful, but WITHOUT ANY
13 WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
15 for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20
21 */
22
23 #include <assert.h>
24 #include <stdlib.h>
25 #include <string.h>
26
27 #include <idzebra/util.h>
28 #include <yaz/yaz-util.h>
29 #include "mfile.h"
30 #include "cfile.h"
31
32 static int write_head(CFile cf)
33 {
34     int left = cf->head.hash_size * sizeof(zint);
35     int bno = 1;
36     int r = 0;
37     const char *tab = (char*) cf->array;
38
39     if (!tab)
40         return 0;
41     while (left >= (int) HASH_BSIZE)
42     {
43         r = mf_write(cf->hash_mf, bno++, 0, 0, tab);
44         if (r)
45             return r;
46         tab += HASH_BSIZE;
47         left -= HASH_BSIZE;
48     }
49     if (left > 0)
50         r = mf_write(cf->hash_mf, bno, 0, left, tab);
51     return r;
52 }
53
54 static int read_head(CFile cf)
55 {
56     int left = cf->head.hash_size * sizeof(zint);
57     int bno = 1;
58     char *tab = (char*) cf->array;
59
60     if (!tab)
61         return 0;
62     while (left >= (int) HASH_BSIZE)
63     {
64         if (mf_read(cf->hash_mf, bno++, 0, 0, tab) == -1)
65             return -1;
66         tab += HASH_BSIZE;
67         left -= HASH_BSIZE;
68     }
69     if (left > 0)
70     {
71         if (mf_read(cf->hash_mf, bno, 0, left, tab) == -1)
72             return -1;
73     }
74     return 1;
75 }
76
77
78 CFile cf_open(MFile mf, MFile_area area, const char *fname,
79               int block_size, int wflag, int *firstp)
80 {
81     char path[1024];
82     int i, ret;
83     CFile cf = (CFile) xmalloc(sizeof(*cf));
84     int hash_bytes;
85
86     yaz_log(YLOG_DEBUG, "cf: open %s %s", cf->rmf->name,
87             wflag ? "rdwr" : "rd");
88    
89     cf->block_mf = 0;
90     cf->hash_mf = 0;
91     cf->rmf = mf; 
92
93     assert(firstp);
94
95     cf->bucket_lru_front = cf->bucket_lru_back = NULL;
96     cf->bucket_in_memory = 0;
97     cf->max_bucket_in_memory = 100;
98     cf->dirty = 0;
99     cf->iobuf = (char *) xmalloc(block_size);
100     memset(cf->iobuf, 0, block_size);
101     cf->no_hits = 0;
102     cf->no_miss = 0;
103     cf->parray = 0;
104     cf->array = 0;
105     cf->block_mf = 0;
106     cf->hash_mf = 0;
107
108     zebra_mutex_init(&cf->mutex);
109
110     sprintf(path, "%s-b", fname);
111     if (!(cf->block_mf = mf_open(area, path, block_size, wflag)))
112     {
113         yaz_log(YLOG_FATAL|YLOG_ERRNO, "Failed to open %s", path);
114         cf_close(cf);
115         return 0;
116     }
117     sprintf(path, "%s-i", fname);
118     if (!(cf->hash_mf = mf_open(area, path, HASH_BSIZE, wflag)))
119     {
120         yaz_log(YLOG_FATAL|YLOG_ERRNO, "Failed to open %s", path);
121         cf_close(cf);
122         return 0;
123     }
124     ret = mf_read(cf->hash_mf, 0, 0, sizeof(cf->head), &cf->head);
125
126     if (ret == -1)
127     {
128         cf_close(cf);
129         return 0;
130     }
131     if (ret == 0 || !cf->head.state)
132     {
133         *firstp = 1;
134         cf->head.state = 1;
135         cf->head.block_size = block_size;
136         cf->head.hash_size = 199;
137         hash_bytes = cf->head.hash_size * sizeof(zint);
138         cf->head.flat_bucket = cf->head.next_bucket = cf->head.first_bucket = 
139             (hash_bytes+sizeof(cf->head))/HASH_BSIZE + 2;
140         cf->head.next_block = 1;
141         cf->array = (zint *) xmalloc(hash_bytes);
142         for (i = 0; i<cf->head.hash_size; i++)
143             cf->array[i] = 0;
144         if (wflag)
145         {
146             if (mf_write(cf->hash_mf, 0, 0, sizeof(cf->head), &cf->head))
147             {
148                 cf_close(cf);
149                 return 0;
150             }
151             if (write_head(cf))
152             {
153                 cf_close(cf);
154                 return 0;
155             }
156         }
157     }
158     else
159     {
160         *firstp = 0;
161         assert(cf->head.block_size == block_size);
162         assert(cf->head.hash_size > 2);
163         hash_bytes = cf->head.hash_size * sizeof(zint);
164         assert(cf->head.next_bucket > 0);
165         assert(cf->head.next_block > 0);
166         if (cf->head.state == 1)
167             cf->array = (zint *) xmalloc(hash_bytes);
168         else
169             cf->array = NULL;
170         if (read_head(cf) == -1)
171         {
172             cf_close(cf);
173             return 0;
174         }
175     }
176     if (cf->head.state == 1)
177     {
178         cf->parray = (struct CFile_hash_bucket **)
179             xmalloc(cf->head.hash_size * sizeof(*cf->parray));
180         for (i = 0; i<cf->head.hash_size; i++)
181             cf->parray[i] = NULL;
182     }
183     return cf;
184 }
185
186 static int cf_hash(CFile cf, zint no)
187 {
188     return (int) (((no >> 3) % cf->head.hash_size));
189 }
190
191 static void release_bucket(CFile cf, struct CFile_hash_bucket *p)
192 {
193     if (p->lru_prev)
194         p->lru_prev->lru_next = p->lru_next;
195     else
196         cf->bucket_lru_back = p->lru_next;
197     if (p->lru_next)
198         p->lru_next->lru_prev = p->lru_prev;
199     else
200         cf->bucket_lru_front = p->lru_prev;
201
202     *p->h_prev = p->h_next;
203     if (p->h_next)
204         p->h_next->h_prev = p->h_prev;
205     
206     --(cf->bucket_in_memory);
207     xfree(p);
208 }
209
210 static void flush_bucket(CFile cf, int no_to_flush)
211 {
212     int i;
213     struct CFile_hash_bucket *p;
214
215     for (i = 0; i != no_to_flush; i++)
216     {
217         p = cf->bucket_lru_back;
218         if (!p)
219             break;
220         if (p->dirty)
221         {
222             mf_write(cf->hash_mf, p->ph.this_bucket, 0, 0, &p->ph);
223             cf->dirty = 1;
224         }
225         release_bucket(cf, p);
226     }
227 }
228
229 static struct CFile_hash_bucket *alloc_bucket(CFile cf, zint block_no, int hno)
230 {
231     struct CFile_hash_bucket *p, **pp;
232
233     if (cf->bucket_in_memory == cf->max_bucket_in_memory)
234         flush_bucket(cf, 1);
235     assert(cf->bucket_in_memory < cf->max_bucket_in_memory);
236     ++(cf->bucket_in_memory);
237     p = (struct CFile_hash_bucket *) xmalloc(sizeof(*p));
238
239     p->lru_next = NULL;
240     p->lru_prev = cf->bucket_lru_front;
241     if (cf->bucket_lru_front)
242         cf->bucket_lru_front->lru_next = p;
243     else
244         cf->bucket_lru_back = p;
245     cf->bucket_lru_front = p; 
246
247     pp = cf->parray + hno;
248     p->h_next = *pp;
249     p->h_prev = pp;
250     if (*pp)
251         (*pp)->h_prev = &p->h_next;
252     *pp = p;
253     return p;
254 }
255
256 static struct CFile_hash_bucket *get_bucket(CFile cf, zint block_no, int hno)
257 {
258     struct CFile_hash_bucket *p;
259
260     p = alloc_bucket(cf, block_no, hno);
261     p->dirty = 0;
262     if (!mf_read(cf->hash_mf, block_no, 0, 0, &p->ph))
263     {
264         yaz_log(YLOG_FATAL|YLOG_ERRNO, "read get_bucket");
265         release_bucket(cf, p);
266         return 0;
267     }
268     assert(p->ph.this_bucket == block_no);
269     return p;
270 }
271
272 static struct CFile_hash_bucket *new_bucket(CFile cf, zint *block_nop, int hno)
273 {
274     struct CFile_hash_bucket *p;
275     int i;
276     zint block_no;
277
278     block_no = *block_nop = cf->head.next_bucket++;
279     p = alloc_bucket(cf, block_no, hno);
280     p->dirty = 1;
281
282     for (i = 0; i<HASH_BUCKET; i++)
283     {
284         p->ph.vno[i] = 0;
285         p->ph.no[i] = 0;
286     }
287     p->ph.next_bucket = 0;
288     p->ph.this_bucket = block_no;
289     return p;
290 }
291
292 static int cf_lookup_flat(CFile cf, zint no, zint *vno)
293 {
294     zint hno = (no*sizeof(zint))/HASH_BSIZE;
295     int off = (int) ((no*sizeof(zint)) - hno*HASH_BSIZE);
296
297     *vno = 0;
298     mf_read(cf->hash_mf, hno+cf->head.next_bucket, off, sizeof(zint), vno);
299     if (*vno)
300         return 1;
301     return 0;
302 }
303
304 static int cf_lookup_hash(CFile cf, zint no, zint *vno)
305 {
306     int hno = cf_hash(cf, no);
307     struct CFile_hash_bucket *hb;
308     zint block_no;
309     int i;
310
311     for (hb = cf->parray[hno]; hb; hb = hb->h_next)
312     {
313         for (i = 0; i<HASH_BUCKET && hb->ph.vno[i]; i++)
314             if (hb->ph.no[i] == no)
315             {
316                 (cf->no_hits)++;
317                 *vno = hb->ph.vno[i];
318                 return 1;
319             }
320     }
321     for (block_no = cf->array[hno]; block_no; block_no = hb->ph.next_bucket)
322     {
323         for (hb = cf->parray[hno]; hb; hb = hb->h_next)
324         {
325             if (hb->ph.this_bucket == block_no)
326                 break;
327         }
328         if (hb)
329             continue;
330 #if 0
331         /* extra check ... */
332         for (hb = cf->bucket_lru_back; hb; hb = hb->lru_next)
333         {
334             if (hb->ph.this_bucket == block_no)
335             {
336                 yaz_log(YLOG_FATAL, "Found hash bucket on other chain(1)");
337                 abort();
338             }
339             for (i = 0; i<HASH_BUCKET && hb->ph.vno[i]; i++)
340                 if (hb->ph.no[i] == no)
341                 {
342                     yaz_log(YLOG_FATAL, "Found hash bucket on other chain (2)");
343                     abort();
344                 }
345         }
346 #endif
347         (cf->no_miss)++;
348         hb = get_bucket(cf, block_no, hno);
349         if (!hb)
350             return -1;
351         for (i = 0; i<HASH_BUCKET && hb->ph.vno[i]; i++)
352             if (hb->ph.no[i] == no)
353             {
354                 *vno = hb->ph.vno[i];
355                 return 1;
356             }
357     }
358     return 0;
359 }
360
361 static int cf_write_flat(CFile cf, zint no, zint vno)
362 {
363     zint hno = (no*sizeof(zint))/HASH_BSIZE;
364     int off = (int) ((no*sizeof(zint)) - hno*HASH_BSIZE);
365
366     hno += cf->head.next_bucket;
367     if (hno >= cf->head.flat_bucket)
368         cf->head.flat_bucket = hno+1;
369     cf->dirty = 1;
370     return mf_write(cf->hash_mf, hno, off, sizeof(zint), &vno);
371 }
372
373 static int cf_moveto_flat(CFile cf)
374 {
375     struct CFile_hash_bucket *p;
376     int j;
377     zint i;
378
379     yaz_log(YLOG_LOG, "cf: Moving to flat shadow: %s", cf->rmf->name);
380     yaz_log(YLOG_DEBUG, "cf: Moving to flat shadow: %s", cf->rmf->name);
381     yaz_log(YLOG_DEBUG, "cf: hits=%d miss=%d bucket_in_memory=" ZINT_FORMAT " total="
382           ZINT_FORMAT,
383         cf->no_hits, cf->no_miss, cf->bucket_in_memory, 
384         cf->head.next_bucket - cf->head.first_bucket);
385     assert(cf->head.state == 1);
386     flush_bucket(cf, -1);
387     assert(cf->bucket_in_memory == 0);
388     p = (struct CFile_hash_bucket *) xmalloc(sizeof(*p));
389     for (i = cf->head.first_bucket; i < cf->head.next_bucket; i++)
390     {
391         if (mf_read(cf->hash_mf, i, 0, 0, &p->ph) != 1)
392         {
393             yaz_log(YLOG_FATAL|YLOG_ERRNO, "read bucket moveto flat");
394             xfree(p);
395             return -1;
396         }
397         for (j = 0; j < HASH_BUCKET && p->ph.vno[j]; j++)
398         {
399             if (cf_write_flat(cf, p->ph.no[j], p->ph.vno[j]))
400             {
401                 xfree(p);
402                 return -1;
403             }
404         }
405     }
406     xfree(p);
407     xfree(cf->array);
408     cf->array = NULL;
409     xfree(cf->parray);
410     cf->parray = NULL;
411     cf->head.state = 2;
412     cf->dirty = 1;
413     return 0;
414 }
415
416 static int cf_lookup(CFile cf, zint no, zint *vno)
417 {
418     if (cf->head.state > 1)
419         return cf_lookup_flat(cf, no, vno);
420     return cf_lookup_hash(cf, no, vno);
421 }
422
423 static zint cf_new_flat(CFile cf, zint no)
424 {
425     zint vno = (cf->head.next_block)++;
426
427     cf_write_flat(cf, no, vno);
428     return vno;
429 }
430
431 static zint cf_new_hash(CFile cf, zint no)
432 {
433     int hno = cf_hash(cf, no);
434     struct CFile_hash_bucket *hbprev = NULL, *hb = cf->parray[hno];
435     zint *bucketpp = &cf->array[hno]; 
436     int i;
437     zint vno = (cf->head.next_block)++;
438   
439     for (hb = cf->parray[hno]; hb; hb = hb->h_next)
440         if (!hb->ph.vno[HASH_BUCKET-1])
441             for (i = 0; i<HASH_BUCKET; i++)
442                 if (!hb->ph.vno[i])
443                 {
444                     (cf->no_hits)++;
445                     hb->ph.no[i] = no;
446                     hb->ph.vno[i] = vno;
447                     hb->dirty = 1;
448                     return vno;
449                 }
450
451     while (*bucketpp)
452     {
453         for (hb = cf->parray[hno]; hb; hb = hb->h_next)
454             if (hb->ph.this_bucket == *bucketpp)
455             {
456                 bucketpp = &hb->ph.next_bucket;
457                 hbprev = hb;
458                 break;
459             }
460         if (hb)
461             continue;
462
463 #if 0
464         /* extra check ... */
465         for (hb = cf->bucket_lru_back; hb; hb = hb->lru_next)
466         {
467             if (hb->ph.this_bucket == *bucketpp)
468             {
469                 yaz_log(YLOG_FATAL, "Found hash bucket on other chain");
470                 abort();
471             }
472         }
473 #endif
474         (cf->no_miss)++;
475         hb = get_bucket(cf, *bucketpp, hno);
476         if (!hb)
477             return 0;
478         for (i = 0; i<HASH_BUCKET; i++)
479             if (!hb->ph.vno[i])
480             {
481                 hb->ph.no[i] = no;
482                 hb->ph.vno[i] = vno;
483                 hb->dirty = 1;
484                 return vno;
485             }
486         bucketpp = &hb->ph.next_bucket;
487         hbprev = hb;
488     }
489     if (hbprev)
490         hbprev->dirty = 1;
491     hb = new_bucket(cf, bucketpp, hno);
492     hb->ph.no[0] = no;
493     hb->ph.vno[0] = vno;
494     return vno;
495 }
496
497 zint cf_new(CFile cf, zint no)
498 {
499     if (cf->head.state > 1)
500         return cf_new_flat(cf, no);
501     if (cf->no_miss*2 > cf->no_hits)
502     {
503         if (cf_moveto_flat(cf))
504             return -1;
505         assert(cf->head.state > 1);
506         return cf_new_flat(cf, no);
507     }
508     return cf_new_hash(cf, no);
509 }
510
511
512 /** \brief reads block from commit area
513     \param cf commit file
514     \param no block number
515     \param offset offset in block
516     \param nbytes number of bytes to read
517     \param buf buffer for content (if read was succesful)
518     \retval 0 block could not be fully read
519     \retval 1 block could be read
520     \retval -1 error
521 */
522 int cf_read(CFile cf, zint no, int offset, int nbytes, void *buf)
523 {
524     zint block;
525     int ret;
526     
527     assert(cf);
528     zebra_mutex_lock(&cf->mutex);
529     ret = cf_lookup(cf, no, &block);
530     zebra_mutex_unlock(&cf->mutex);
531     if (ret != 1)
532     {
533         /* block could not be read or error */
534         return ret;
535     }
536     if (mf_read(cf->block_mf, block, offset, nbytes, buf) != 1)
537     {
538         yaz_log(YLOG_FATAL|YLOG_ERRNO, "cf_read no=" ZINT_FORMAT " block=" ZINT_FORMAT, no, block);
539         return -1;
540     }
541     return 1;
542 }
543
544 /** \brief writes block to commit area
545     \param cf commit file
546     \param no block number
547     \param offset offset in block
548     \param nbytes number of bytes to be written
549     \param buf buffer to be written
550     \retval 0 block written
551     \retval -1 error
552 */
553 int cf_write(CFile cf, zint no, int offset, int nbytes, const void *buf)
554 {
555     zint block;
556     int ret;
557
558     assert(cf);
559     zebra_mutex_lock(&cf->mutex);
560
561     ret = cf_lookup(cf, no, &block);
562
563     if (ret == -1)
564     {
565         zebra_mutex_unlock(&cf->mutex);
566         return ret;
567     }
568     if (ret == 0)
569     {
570         block = cf_new(cf, no);
571         if (!block)
572         {
573             zebra_mutex_unlock(&cf->mutex);
574             return -1;
575         }
576         if (offset || nbytes)
577         {
578             mf_read(cf->rmf, no, 0, 0, cf->iobuf);
579             memcpy(cf->iobuf + offset, buf, nbytes);
580             buf = cf->iobuf;
581             offset = 0;
582             nbytes = 0;
583         }
584     }
585     zebra_mutex_unlock(&cf->mutex);
586     return mf_write(cf->block_mf, block, offset, nbytes, buf);
587 }
588
589 int cf_close(CFile cf)
590 {
591     yaz_log(YLOG_DEBUG, "cf: close hits=%d miss=%d bucket_in_memory=" ZINT_FORMAT
592           " total=" ZINT_FORMAT,
593           cf->no_hits, cf->no_miss, cf->bucket_in_memory,
594           cf->head.next_bucket - cf->head.first_bucket);
595     flush_bucket(cf, -1);
596     if (cf->hash_mf)
597     {
598         if (cf->dirty)
599         {
600             mf_write(cf->hash_mf, 0, 0, sizeof(cf->head), &cf->head);
601             write_head(cf);
602         }
603         mf_close(cf->hash_mf);
604     }
605     if (cf->block_mf)
606         mf_close(cf->block_mf);
607     xfree(cf->array);
608     xfree(cf->parray);
609     xfree(cf->iobuf);
610     zebra_mutex_destroy(&cf->mutex);
611     xfree(cf);
612     return 0;
613 }
614
615 /*
616  * Local variables:
617  * c-basic-offset: 4
618  * indent-tabs-mode: nil
619  * End:
620  * vim: shiftwidth=4 tabstop=8 expandtab
621  */
622