chiark / gitweb /
Commit 2.4.5-5 as unpacked
[inn-innduct.git] / storage / buffindexed / buffindexed.c
1 /*  $Id: buffindexed.c 7602 2007-02-10 22:19:49Z eagle $
2 **
3 **  Overview buffer and index method.
4 */
5
6 #include "config.h"
7 #include "clibrary.h"
8 #include "portable/mmap.h"
9 #include <assert.h>
10 #include <ctype.h>
11 #include <errno.h>
12 #include <fcntl.h>
13 #if HAVE_LIMITS_H
14 # include <limits.h>
15 #endif
16 #include <syslog.h>
17 #include <sys/stat.h>
18 #include <time.h>
19
20 #include "inn/innconf.h"
21 #include "libinn.h"
22 #include "ov.h"
23 #include "paths.h"
24 #include "ovinterface.h"
25 #include "storage.h"
26
27 #include "buffindexed.h"
28
29 #define OVBUFF_MAGIC    "ovbuff"
30
31 /* ovbuff header */
32 #define OVBUFFMASIZ     8
33 #define OVBUFFNASIZ     16
34 #define OVBUFFLASIZ     16
35 #define OVBUFFPASIZ     64
36
37 #define OVMAXCYCBUFFNAME        8
38
39 #define OV_HDR_PAGESIZE 16384
40 #define OV_BLOCKSIZE    8192
41 #define OV_BEFOREBITF   (1 * OV_BLOCKSIZE)
42 #define OV_FUDGE        1024
43
44 /* ovblock pointer */
45 typedef struct _OV {
46   unsigned int  blocknum;
47   short         index;
48 } OV;
49
50 /* ovbuff header */
51 typedef struct {
52   char  magic[OVBUFFMASIZ];
53   char  path[OVBUFFPASIZ];
54   char  indexa[OVBUFFLASIZ];    /* ASCII version of index */
55   char  lena[OVBUFFLASIZ];      /* ASCII version of len */
56   char  totala[OVBUFFLASIZ];    /* ASCII version of total */
57   char  useda[OVBUFFLASIZ];     /* ASCII version of used */
58   char  freea[OVBUFFLASIZ];     /* ASCII version of free */
59   char  updateda[OVBUFFLASIZ];  /* ASCII version of updated */
60 } OVBUFFHEAD;
61
62 /* ovbuff info */
63 typedef struct _OVBUFF {
64   unsigned int          index;                  /* ovbuff index */
65   char                  path[OVBUFFPASIZ];      /* Path to file */
66   int                   magicver;               /* Magic version number */
67   int                   fd;                     /* file descriptor for this
68                                                    ovbuff */
69   off_t                 len;                    /* Length of writable area, in
70                                                    bytes */
71   off_t                 base;                   /* Offset (relative to byte
72                                                    0 of file) to base block */
73   unsigned int          freeblk;                /* next free block number no
74                                                    freeblk left if equals
75                                                    totalblk */
76   unsigned int          totalblk;               /* number of total blocks */
77   unsigned int          usedblk;                /* number of used blocks */
78   time_t                updated;                /* Time of last update to
79                                                    header */
80   void *                bitfield;               /* Bitfield for ovbuff block in
81                                                    use */
82   bool                  needflush;              /* true if OVBUFFHEAD is needed
83                                                    to be flushed */
84   struct _OVBUFF        *next;                  /* next ovbuff */
85   int                   nextchunk;              /* next chunk */
86 #ifdef OV_DEBUG
87   struct ov_trace_array *trace;
88 #endif /* OV_DEBUG */
89 } OVBUFF;
90
91 typedef struct _OVINDEXHEAD {
92   OV            next;           /* next block */
93   ARTNUM        low;            /* lowest article number in the index */
94   ARTNUM        high;           /* highest article number in the index */
95 } OVINDEXHEAD;
96
97 typedef struct _OVINDEX {
98   ARTNUM        artnum;         /* article number */
99   unsigned int  blocknum;       /* overview data block number */
100   short         index;          /* overview data block index */
101   TOKEN         token;          /* token for this article */
102   off_t         offset;         /* offset from the top in the block */
103   int           len;            /* length of the data */
104   time_t        arrived;        /* arrived time of article */
105   time_t        expires;        /* expire time of article */
106 } OVINDEX;
107
108 #define OVINDEXMAX      ((OV_BLOCKSIZE-sizeof(OVINDEXHEAD))/sizeof(OVINDEX))
109
110 typedef struct _OVBLOCK {
111   OVINDEXHEAD   ovindexhead;            /* overview index header */
112   OVINDEX       ovindex[OVINDEXMAX];    /* overview index */
113 } OVBLOCK;
114
115 typedef struct _OVBLKS {
116   OVBLOCK       *ovblock;
117   void *        addr;
118   int           len;
119   OV            indexov;
120 } OVBLKS;
121
122 /* Data structure for specifying a location in the group index */
123 typedef struct {
124     int                 recno;             /* Record number in group index */
125 } GROUPLOC;
126
127 #ifdef OV_DEBUG
128 struct ov_trace {
129   time_t        occupied;
130   time_t        freed;
131   GROUPLOC      gloc;
132 };
133
134 #define OV_TRACENUM 10
135 struct ov_trace_array {
136   int                   max;
137   int                   cur;
138   struct ov_trace       *ov_trace;
139 };
140
141 struct ov_name_table {
142   char                  *name;
143   int                   recno;
144   struct ov_name_table  *next;
145 };
146
147 static struct ov_name_table *name_table = NULL;
148 #endif /* OV_DEBUG */
149
150 #define GROUPHEADERHASHSIZE (16 * 1024)
151 #define GROUPHEADERMAGIC    (~(0xf1f0f33d))
152
153 typedef struct {
154   int           magic;
155   GROUPLOC      hash[GROUPHEADERHASHSIZE];
156   GROUPLOC      freelist;
157 } GROUPHEADER;
158
159 /* The group is matched based on the MD5 of the grouname. This may prove to
160    be inadequate in the future, if so, the right thing to do is to is
161    probably just to add a SHA1 hash into here also.  We get a really nice
162    benefit from this being fixed length, we should try to keep it that way.
163 */
164 typedef struct {
165   HASH          hash;           /* MD5 hash of the group name */
166   HASH          alias;          /* If not empty then this is the hash of the
167                                    group that this group is an alias for */
168   ARTNUM        high;           /* High water mark in group */
169   ARTNUM        low;            /* Low water mark in group */
170   int           count;          /* Number of articles in group */
171   int           flag;           /* Posting/Moderation Status */
172   time_t        expired;        /* When last expiry */
173   time_t        deleted;        /* When this was deleted, 0 otherwise */
174   GROUPLOC      next;           /* Next block in this chain */
175   OV            baseindex;      /* base index buff */
176   OV            curindex;       /* current index buff */
177   int           curindexoffset; /* current index offset for this ovbuff */
178   ARTNUM        curhigh;        /* High water mark in group */
179   ARTNUM        curlow;         /* Low water mark in group */
180   OV            curdata;        /* current offset for this ovbuff */
181   off_t         curoffset;      /* current offset for this ovbuff */
182 } GROUPENTRY;
183
184 typedef struct _GIBLIST {
185   OV                    ov;
186   struct _GIBLIST       *next;
187 } GIBLIST;
188
189 typedef struct _GDB {
190   OV            datablk;
191   void *        addr;
192   void *        data;
193   int           len;
194   bool          mmapped;
195   struct _GDB   *next;
196 } GROUPDATABLOCK;
197
198 typedef struct {
199   char                  *group;
200   int                   lo;
201   int                   hi;
202   int                   cur;
203   bool                  needov;
204   GROUPLOC              gloc;
205   int                   count;
206   GROUPDATABLOCK        gdb;    /* used for caching current block */
207 } OVSEARCH;
208
209 #define GROUPDATAHASHSIZE       25
210
211 static GROUPDATABLOCK   *groupdatablock[GROUPDATAHASHSIZE];
212
213 typedef enum {PREPEND_BLK, APPEND_BLK} ADDINDEX;
214 typedef enum {SRCH_FRWD, SRCH_BKWD} SRCH;
215
216 #define _PATH_OVBUFFCONFIG      "buffindexed.conf"
217
218 static char LocalLogName[] = "buffindexed";
219 static long             pagesize = 0;
220 static OVBUFF           *ovbufftab;
221 static int              GROUPfd;
222 static GROUPHEADER      *GROUPheader = NULL;
223 static GROUPENTRY       *GROUPentries = NULL;
224 static int              GROUPcount = 0;
225 static GROUPLOC         GROUPemptyloc = { -1 };
226 #define NULLINDEX       (-1)
227 static OV               ovnull = { 0, NULLINDEX };
228 typedef unsigned long   ULONG;
229 static ULONG            onarray[64], offarray[64];
230 static int              longsize = sizeof(long);
231 static bool             Nospace;
232 static bool             Needunlink;
233 static bool             Cutofflow;
234 static bool             Cache;
235 static OVSEARCH         *Cachesearch;
236
237 static int ovbuffmode;
238
239 static GROUPLOC GROUPnewnode(void);
240 static bool GROUPremapifneeded(GROUPLOC loc);
241 static void GROUPLOCclear(GROUPLOC *loc);
242 static bool GROUPLOCempty(GROUPLOC loc);
243 static bool GROUPlockhash(enum inn_locktype type);
244 static bool GROUPlock(GROUPLOC gloc, enum inn_locktype type);
245 static off_t GROUPfilesize(int count);
246 static bool GROUPexpand(int mode);
247 static void *ovopensearch(char *group, int low, int high, bool needov);
248 static void ovclosesearch(void *handle, bool freeblock);
249 static OVINDEX  *Gib;
250 static GIBLIST  *Giblist;
251 static int      Gibcount;
252
253 #ifdef MMAP_MISSES_WRITES
254 /* With HP/UX, you definitely do not want to mix mmap-accesses of
255    a file with read()s and write()s of the same file */
256 static off_t mmapwrite(int fd, void *buf, off_t nbyte, off_t offset) {
257   int           pagefudge, len;
258   off_t         mmapoffset;
259   void *        addr;
260
261   pagefudge = offset % pagesize;
262   mmapoffset = offset - pagefudge;
263   len = pagefudge + nbyte;
264
265   if ((addr = mmap(NULL, len, PROT_READ|PROT_WRITE, MAP_SHARED, fd, mmapoffset)) == MAP_FAILED) {
266     return -1;
267   }
268   memcpy(addr+pagefudge, buf, nbyte);
269   munmap(addr, len);
270   return nbyte;
271 }
272 #endif /* MMAP_MISSES_WRITES */
273
274 static bool ovparse_part_line(char *l) {
275   char          *p;
276   struct stat   sb;
277   off_t         len, base;
278   int           tonextblock;
279   OVBUFF        *ovbuff, *tmp = ovbufftab;
280
281   /* ovbuff partition name */
282   if ((p = strchr(l, ':')) == NULL || p - l <= 0 || p - l > OVMAXCYCBUFFNAME - 1) {
283     syslog(L_ERROR, "%s: bad index in line '%s'", LocalLogName, l);
284     return false;
285   }
286   *p = '\0';
287   ovbuff = xmalloc(sizeof(OVBUFF));
288   ovbuff->index = strtoul(l, NULL, 10);
289   for (; tmp != (OVBUFF *)NULL; tmp = tmp->next) {
290     if (tmp->index == ovbuff->index) {
291       syslog(L_ERROR, "%s: dupulicate index in line '%s'", LocalLogName, l);
292       free(ovbuff);
293       return false;
294     }
295   }
296   l = ++p;
297
298   /* Path to ovbuff partition */
299   if ((p = strchr(l, ':')) == NULL || p - l <= 0 || p - l > OVBUFFPASIZ - 1) {
300     syslog(L_ERROR, "%s: bad pathname in line '%s'", LocalLogName, l);
301     free(ovbuff);
302     return false;
303   }
304   *p = '\0';
305   memset(ovbuff->path, '\0', OVBUFFPASIZ);
306   strlcpy(ovbuff->path, l, OVBUFFPASIZ);
307   if (stat(ovbuff->path, &sb) < 0) {
308     syslog(L_ERROR, "%s: file '%s' does not exist, ignoring '%d'",
309            LocalLogName, ovbuff->path, ovbuff->index);
310     free(ovbuff);
311     return false;
312   }
313   l = ++p;
314
315   /* Length/size of symbolic partition in KB */
316   len = strtoul(l, NULL, 10) * (off_t) 1024;
317   /*
318   ** The minimum article offset will be the size of the bitfield itself,
319   ** len / (blocksize * 8), plus however many additional blocks the OVBUFFHEAD
320   ** external header occupies ... then round up to the next block.
321   */
322   base = len / (OV_BLOCKSIZE * 8) + OV_BEFOREBITF;
323   tonextblock = OV_HDR_PAGESIZE - (base & (OV_HDR_PAGESIZE - 1));
324   ovbuff->base = base + tonextblock;
325   if (S_ISREG(sb.st_mode) && (len != sb.st_size || ovbuff->base > sb.st_size)) {
326     if (len != sb.st_size)
327       syslog(L_NOTICE, "%s: length mismatch '%lu' for index '%d' (%lu bytes)",
328         LocalLogName, (unsigned long) len, ovbuff->index,
329         (unsigned long) sb.st_size);
330     if (ovbuff->base > sb.st_size)
331       syslog(L_NOTICE, "%s: length must be at least '%lu' for index '%d' (%lu bytes)",
332         LocalLogName, (unsigned long) ovbuff->base, ovbuff->index,
333         (unsigned long) sb.st_size);
334     free(ovbuff);
335     return false;
336   }
337   ovbuff->len = len;
338   ovbuff->fd = -1;
339   ovbuff->next = (OVBUFF *)NULL;
340   ovbuff->needflush = false;
341   ovbuff->bitfield = NULL;
342   ovbuff->nextchunk = 1;
343
344   if (ovbufftab == (OVBUFF *)NULL)
345     ovbufftab = ovbuff;
346   else {
347     for (tmp = ovbufftab; tmp->next != (OVBUFF *)NULL; tmp = tmp->next);
348     tmp->next = ovbuff;
349   }
350   return true;
351 }
352
353 /*
354 ** ovbuffread_config() -- Read the overview partition/file configuration file.
355 */
356
357 static bool ovbuffread_config(void) {
358   char          *path, *config, *from, *to, **ctab = (char **)NULL;
359   int           ctab_free = 0;  /* Index to next free slot in ctab */
360   int           ctab_i;
361
362   path = concatpath(innconf->pathetc, _PATH_OVBUFFCONFIG);
363   config = ReadInFile(path, NULL);
364   if (config == NULL) {
365     syslog(L_ERROR, "%s: cannot read %s", LocalLogName, path);
366     free(config);
367     free(path);
368     return false;
369   }
370   free(path);
371   for (from = to = config; *from; ) {
372     if (*from == '#') { /* Comment line? */
373       while (*from && *from != '\n')
374         from++; /* Skip past it */
375       from++;
376       continue; /* Back to top of loop */
377     }
378     if (*from == '\n') {        /* End or just a blank line? */
379       from++;
380       continue;         /* Back to top of loop */
381     }
382     if (ctab_free == 0)
383       ctab = xmalloc(sizeof(char *));
384     else
385       ctab = xrealloc(ctab, (ctab_free + 1) * sizeof(char *));
386     /* If we're here, we've got the beginning of a real entry */
387     ctab[ctab_free++] = to = from;
388     while (1) {
389       if (*from && *from == '\\' && *(from + 1) == '\n') {
390         from += 2;      /* Skip past backslash+newline */
391         while (*from && isspace((int)*from))
392           from++;
393         continue;
394       }
395       if (*from && *from != '\n')
396         *to++ = *from++;
397       if (*from == '\n') {
398         *to++ = '\0';
399         from++;
400         break;
401       }
402       if (! *from)
403         break;
404     }
405   }
406   for (ctab_i = 0; ctab_i < ctab_free; ctab_i++) {
407     if (!ovparse_part_line(ctab[ctab_i])) {
408       free(config);
409       free(ctab);
410       return false;
411     }
412   }
413   free(config);
414   free(ctab);
415   if (ovbufftab == (OVBUFF *)NULL) {
416     syslog(L_ERROR, "%s: no buffindexed defined", LocalLogName);
417     return false;
418   }
419   return true;
420 }
421
422 static char hextbl[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
423                         'a', 'b', 'c', 'd', 'e', 'f'};
424
425 static char *offt2hex(off_t offset, bool leadingzeros) {
426   static char   buf[24];
427   char  *p;
428
429   if (sizeof(off_t) <= 4) {
430     snprintf(buf, sizeof(buf), (leadingzeros) ? "%016lx" : "%lx", offset);
431   } else {
432     int i;
433
434     for (i = 0; i < OVBUFFLASIZ; i++)
435       buf[i] = '0';     /* Pad with zeros to start */
436     for (i = OVBUFFLASIZ - 1; i >= 0; i--) {
437       buf[i] = hextbl[offset & 0xf];
438       offset >>= 4;
439     }
440   }
441   if (!leadingzeros) {
442     for (p = buf; *p == '0'; p++)
443             ;
444     if (*p != '\0')
445       return p;
446     else
447       return p - 1;     /* We converted a "0" and then bypassed all the zeros */
448   } else
449     return buf;
450 }
451
452 static off_t hex2offt(char *hex) {
453   if (sizeof(off_t) <= 4) {
454     unsigned long rpofft;
455
456     sscanf(hex, "%lx", &rpofft);
457     return rpofft;
458   } else {
459     char                diff;
460     off_t       n = 0;
461
462     for (; *hex != '\0'; hex++) {
463       if (*hex >= '0' && *hex <= '9')
464         diff = '0';
465       else if (*hex >= 'a' && *hex <= 'f')
466         diff = 'a' - 10;
467       else if (*hex >= 'A' && *hex <= 'F')
468         diff = 'A' - 10;
469       else {
470         /*
471         ** We used to have a syslog() message here, but the case
472         ** where we land here because of a ":" happens, er, often.
473         */
474         break;
475       }
476       n += (*hex - diff);
477       if (isalnum((int)*(hex + 1)))
478         n <<= 4;
479     }
480     return n;
481   }
482 }
483
484 static void ovreadhead(OVBUFF *ovbuff) {
485   OVBUFFHEAD    rpx;
486   char          buff[OVBUFFLASIZ+1];
487
488   memcpy(&rpx, ovbuff->bitfield, sizeof(OVBUFFHEAD));
489   strncpy(buff, rpx.useda, OVBUFFLASIZ);
490   buff[OVBUFFLASIZ] = '\0';
491   ovbuff->usedblk = (unsigned int)hex2offt((char *)buff);
492   strncpy(buff, rpx.freea, OVBUFFLASIZ);
493   buff[OVBUFFLASIZ] = '\0';
494   ovbuff->freeblk = (unsigned int)hex2offt((char *)buff);
495   return;
496 }
497
498 static void ovflushhead(OVBUFF *ovbuff) {
499   OVBUFFHEAD    rpx;
500
501   if (!ovbuff->needflush)
502     return;
503   memset(&rpx, 0, sizeof(OVBUFFHEAD));
504   ovbuff->updated = time(NULL);
505   strncpy(rpx.magic, OVBUFF_MAGIC, strlen(OVBUFF_MAGIC));
506   strncpy(rpx.path, ovbuff->path, OVBUFFPASIZ);
507   /* Don't use sprintf() directly ... the terminating '\0' causes grief */
508   strncpy(rpx.indexa, offt2hex(ovbuff->index, true), OVBUFFLASIZ);
509   strncpy(rpx.lena, offt2hex(ovbuff->len, true), OVBUFFLASIZ);
510   strncpy(rpx.totala, offt2hex(ovbuff->totalblk, true), OVBUFFLASIZ);
511   strncpy(rpx.useda, offt2hex(ovbuff->usedblk, true), OVBUFFLASIZ);
512   strncpy(rpx.freea, offt2hex(ovbuff->freeblk, true), OVBUFFLASIZ);
513   strncpy(rpx.updateda, offt2hex(ovbuff->updated, true), OVBUFFLASIZ);
514   memcpy(ovbuff->bitfield, &rpx, sizeof(OVBUFFHEAD));
515   mmap_flush(ovbuff->bitfield, ovbuff->base);
516   ovbuff->needflush = false;
517   return;
518 }
519
520 static bool ovlock(OVBUFF *ovbuff, enum inn_locktype type) {
521   return inn_lock_range(ovbuff->fd, type, true, 0, sizeof(OVBUFFHEAD));
522 }
523
524 static bool ovbuffinit_disks(void) {
525   OVBUFF        *ovbuff = ovbufftab;
526   char          buf[64];
527   OVBUFFHEAD    *rpx;
528   int           i, fd;
529   off_t         tmpo;
530
531   /*
532   ** Discover the state of our ovbuffs.  If any of them are in icky shape,
533   ** duck shamelessly & return false.
534   */
535   for (; ovbuff != (OVBUFF *)NULL; ovbuff = ovbuff->next) {
536     if (ovbuff->fd < 0) {
537       if ((fd = open(ovbuff->path, ovbuffmode & OV_WRITE ? O_RDWR : O_RDONLY)) < 0) {
538         syslog(L_ERROR, "%s: ERROR opening '%s' : %m", LocalLogName, ovbuff->path);
539         return false;
540       } else {
541         close_on_exec(fd, true);
542         ovbuff->fd = fd;
543       }
544     }
545     if ((ovbuff->bitfield =
546          mmap(NULL, ovbuff->base, ovbuffmode & OV_WRITE ? (PROT_READ | PROT_WRITE) : PROT_READ,
547               MAP_SHARED, ovbuff->fd, (off_t) 0)) == MAP_FAILED) {
548       syslog(L_ERROR,
549                "%s: ovinitdisks: mmap for %s offset %d len %lu failed: %m",
550                LocalLogName, ovbuff->path, 0, (unsigned long) ovbuff->base);
551       return false;
552     }
553     rpx = (OVBUFFHEAD *)ovbuff->bitfield;
554     ovlock(ovbuff, INN_LOCK_WRITE);
555     if (strncmp(rpx->magic, OVBUFF_MAGIC, strlen(OVBUFF_MAGIC)) == 0) {
556         ovbuff->magicver = 1;
557         if (strncmp(rpx->path, ovbuff->path, OVBUFFPASIZ) != 0) {
558           syslog(L_ERROR, "%s: Path mismatch: read %s for buffindexed %s",
559                    LocalLogName, rpx->path, ovbuff->path);
560           ovbuff->needflush = true;
561         }
562         strncpy(buf, rpx->indexa, OVBUFFLASIZ);
563         buf[OVBUFFLASIZ] = '\0';
564         i = hex2offt(buf);
565         if (i != ovbuff->index) {
566             syslog(L_ERROR, "%s: Mismatch: index '%d' for buffindexed %s",
567                    LocalLogName, i, ovbuff->path);
568             ovlock(ovbuff, INN_LOCK_UNLOCK);
569             return false;
570         }
571         strncpy(buf, rpx->lena, OVBUFFLASIZ);
572         buf[OVBUFFLASIZ] = '\0';
573         tmpo = hex2offt(buf);
574         if (tmpo != ovbuff->len) {
575             syslog(L_ERROR, "%s: Mismatch: read 0x%s length for buffindexed %s",
576                    LocalLogName, offt2hex(tmpo, false), ovbuff->path);
577             ovlock(ovbuff, INN_LOCK_UNLOCK);
578             return false;
579         }
580         strncpy(buf, rpx->totala, OVBUFFLASIZ);
581         buf[OVBUFFLASIZ] = '\0';
582         ovbuff->totalblk = hex2offt(buf);
583         strncpy(buf, rpx->useda, OVBUFFLASIZ);
584         buf[OVBUFFLASIZ] = '\0';
585         ovbuff->usedblk = hex2offt(buf);
586         strncpy(buf, rpx->freea, OVBUFFLASIZ);
587         buf[OVBUFFLASIZ] = '\0';
588         ovbuff->freeblk = hex2offt(buf);
589         ovflushhead(ovbuff);
590         Needunlink = false;
591     } else {
592         ovbuff->totalblk = (ovbuff->len - ovbuff->base)/OV_BLOCKSIZE;
593         if (ovbuff->totalblk < 1) {
594           syslog(L_ERROR, "%s: too small length '%lu' for buffindexed %s",
595             LocalLogName, (unsigned long) ovbuff->len, ovbuff->path);
596           ovlock(ovbuff, INN_LOCK_UNLOCK);
597           return false;
598         }
599         ovbuff->magicver = 1;
600         ovbuff->usedblk = 0;
601         ovbuff->freeblk = 0;
602         ovbuff->updated = 0;
603         ovbuff->needflush = true;
604         syslog(L_NOTICE,
605                 "%s: No magic cookie found for buffindexed %d, initializing",
606                 LocalLogName, ovbuff->index);
607         ovflushhead(ovbuff);
608     }
609 #ifdef OV_DEBUG
610     ovbuff->trace = xcalloc(ovbuff->totalblk, sizeof(ov_trace_array));
611 #endif /* OV_DEBUG */
612     ovlock(ovbuff, INN_LOCK_UNLOCK);
613   }
614   return true;
615 }
616
617 static int ovusedblock(OVBUFF *ovbuff, int blocknum, bool set_operation, bool setbitvalue) {
618   off_t         longoffset;
619   int           bitoffset;      /* From the 'left' side of the long */
620   ULONG         bitlong, mask;
621
622   longoffset = blocknum / (sizeof(long) * 8);
623   bitoffset = blocknum % (sizeof(long) * 8);
624   bitlong = *((ULONG *) ovbuff->bitfield + (OV_BEFOREBITF / sizeof(long))
625                 + longoffset);
626   if (set_operation) {
627     if (setbitvalue) {
628       mask = onarray[bitoffset];
629       bitlong |= mask;
630     } else {
631       mask = offarray[bitoffset];
632       bitlong &= mask;
633     }
634     *((ULONG *) ovbuff->bitfield + (OV_BEFOREBITF / sizeof(long))
635       + longoffset) = bitlong;
636     return 2;   /* XXX Clean up return semantics */
637   }
638   /* It's a read operation */
639   mask = onarray[bitoffset];
640   /* return bitlong & mask; doesn't work if sizeof(ulong) > sizeof(int) */
641   if ( bitlong & mask ) return 1; else return 0;
642 }
643
644 static void ovnextblock(OVBUFF *ovbuff) {
645   int           i, j, last, lastbit, left;
646   ULONG         mask = 0x80000000;
647   ULONG         *table;
648
649   last = ovbuff->totalblk/(sizeof(long) * 8);
650   if ((left = ovbuff->totalblk % (sizeof(long) * 8)) != 0) {
651     last++;
652   }
653   table = ((ULONG *) ovbuff->bitfield + (OV_BEFOREBITF / sizeof(long)));
654   for (i = ovbuff->nextchunk ; i < last ; i++) {
655     if (i == last - 1 && left != 0) {
656       for (j = 1 ; j < left ; j++) {
657         mask |= mask >> 1;
658       }
659       if ((table[i] & mask) != mask)
660         break;
661     } else {
662       if ((table[i] ^ ~0) != 0)
663         break;
664     }
665   }
666   if (i == last) {
667     for (i = 0 ; i < ovbuff->nextchunk ; i++) {
668       if ((table[i] ^ ~0) != 0)
669         break;
670     }
671     if (i == ovbuff->nextchunk) {
672       ovbuff->freeblk = ovbuff->totalblk;
673       return;
674     }
675   }
676   if ((i - 1) >= 0 && (last - 1 == i) && left != 0) {
677     lastbit = left;
678   } else {
679     lastbit = sizeof(long) * 8;
680   }
681   for (j = 0 ; j < lastbit ; j++) {
682     if ((table[i] & onarray[j]) == 0)
683       break;
684   }
685   if (j == lastbit) {
686     ovbuff->freeblk = ovbuff->totalblk;
687     return;
688   }
689   ovbuff->freeblk = i * sizeof(long) * 8 + j;
690   ovbuff->nextchunk = i + 1;
691   if (i == last)
692     ovbuff->nextchunk = 0;
693   return;
694 }
695
696 static OVBUFF *getovbuff(OV ov) {
697   OVBUFF        *ovbuff = ovbufftab;
698   for (; ovbuff != (OVBUFF *)NULL; ovbuff = ovbuff->next) {
699     if (ovbuff->index == ov.index)
700       return ovbuff;
701   }
702   return NULL;
703 }
704
705 #ifdef OV_DEBUG
706 static OV ovblocknew(GROUPENTRY *ge) {
707 #else
708 static OV ovblocknew(void) {
709 #endif /* OV_DEBUG */
710   static OVBUFF *ovbuffnext = NULL;
711   OVBUFF        *ovbuff;
712   OV            ov;
713 #ifdef OV_DEBUG
714   int           recno;
715   struct ov_trace_array *trace;
716 #endif /* OV_DEBUG */
717
718   if (ovbuffnext == NULL)
719     ovbuffnext = ovbufftab;
720   for (ovbuff = ovbuffnext ; ovbuff != (OVBUFF *)NULL ; ovbuff = ovbuff->next) {
721     ovlock(ovbuff, INN_LOCK_WRITE);
722     ovreadhead(ovbuff);
723     if (ovbuff->totalblk != ovbuff->usedblk && ovbuff->freeblk == ovbuff->totalblk) {
724       ovnextblock(ovbuff);
725     }
726     if (ovbuff->totalblk == ovbuff->usedblk || ovbuff->freeblk == ovbuff->totalblk) {
727       /* no space left for this ovbuff */
728       ovlock(ovbuff, INN_LOCK_UNLOCK);
729       continue;
730     }
731     break;
732   }
733   if (ovbuff == NULL) {
734     for (ovbuff = ovbufftab ; ovbuff != ovbuffnext ; ovbuff = ovbuff->next) {
735       ovlock(ovbuff, INN_LOCK_WRITE);
736       ovreadhead(ovbuff);
737       if (ovbuff->totalblk == ovbuff->usedblk || ovbuff->freeblk == ovbuff->totalblk) {
738         /* no space left for this ovbuff */
739         ovlock(ovbuff, INN_LOCK_UNLOCK);
740         continue;
741       }
742       break;
743     }
744     if (ovbuff == ovbuffnext) {
745       Nospace = true;
746       return ovnull;
747     }
748   }
749 #ifdef OV_DEBUG
750   recno = ((char *)ge - (char *)&GROUPentries[0])/sizeof(GROUPENTRY);
751   if (ovusedblock(ovbuff, ovbuff->freeblk, false, true)) {
752     syslog(L_FATAL, "%s: 0x%08x trying to occupy new block(%d, %d), but already occupied", LocalLogName, recno, ovbuff->index, ovbuff->freeblk);
753     buffindexed_close();
754     abort();
755   }
756   trace = &ovbuff->trace[ovbuff->freeblk];
757   if (trace->ov_trace == NULL) {
758     trace->ov_trace = xcalloc(OV_TRACENUM, sizeof(struct ov_trace));
759     trace->max = OV_TRACENUM;
760   } else if (trace->cur + 1 == trace->max) {
761     trace->max += OV_TRACENUM;
762     trace->ov_trace = xrealloc(trace->ov_trace, trace->max * sizeof(struct ov_trace));
763     memset(&trace->ov_trace[trace->cur], '\0', sizeof(struct ov_trace) * (trace->max - trace->cur));
764   }
765   if (trace->ov_trace[trace->cur].occupied != 0) {
766     trace->cur++;
767   }
768   trace->ov_trace[trace->cur].gloc.recno = recno;
769   trace->ov_trace[trace->cur].occupied = time(NULL);
770 #endif /* OV_DEBUG */
771   ov.index = ovbuff->index;
772   ov.blocknum = ovbuff->freeblk;
773   ovusedblock(ovbuff, ov.blocknum, true, true);
774   ovnextblock(ovbuff);
775   ovbuff->usedblk++;
776   ovbuff->needflush = true;
777   ovflushhead(ovbuff);
778   ovlock(ovbuff, INN_LOCK_UNLOCK);
779   ovbuffnext = ovbuff->next;
780   if (ovbuffnext == NULL)
781     ovbuffnext = ovbufftab;
782   return ov;
783 }
784
785 #ifdef OV_DEBUG
786 static void ovblockfree(OV ov, GROUPENTRY *ge) {
787 #else
788 static void ovblockfree(OV ov) {
789 #endif /* OV_DEBUG */
790   OVBUFF        *ovbuff;
791 #ifdef OV_DEBUG
792   int           recno;
793   struct ov_trace_array *trace;
794 #endif /* OV_DEBUG */
795
796   if (ov.index == NULLINDEX)
797     return;
798   if ((ovbuff = getovbuff(ov)) == NULL)
799     return;
800   ovlock(ovbuff, INN_LOCK_WRITE);
801 #ifdef OV_DEBUG
802   recno = ((char *)ge - (char *)&GROUPentries[0])/sizeof(GROUPENTRY);
803   if (!ovusedblock(ovbuff, ov.blocknum, false, false)) {
804     syslog(L_FATAL, "%s: 0x%08x trying to free block(%d, %d), but already freed", LocalLogName, recno, ov.index, ov.blocknum);
805     buffindexed_close();
806     abort();
807   }
808   trace = &ovbuff->trace[ov.blocknum];
809   if (trace->ov_trace == NULL) {
810     trace->ov_trace = xcalloc(OV_TRACENUM, sizeof(struct ov_trace));
811     trace->max = OV_TRACENUM;
812   } else if (trace->cur + 1 == trace->max) {
813     trace->max += OV_TRACENUM;
814     trace->ov_trace = xrealloc(trace->ov_trace, trace->max * sizeof(struct ov_trace));
815     memset(&trace->ov_trace[trace->cur], '\0', sizeof(struct ov_trace) * (trace->max - trace->cur));
816   }
817   if (trace->ov_trace[trace->cur].freed != 0) {
818     trace->cur++;
819   }
820   trace->ov_trace[trace->cur].freed = time(NULL);
821   trace->ov_trace[trace->cur].gloc.recno = recno;
822   trace->cur++;
823 #endif /* OV_DEBUG */
824   ovusedblock(ovbuff, ov.blocknum, true, false);
825   ovreadhead(ovbuff);
826   if (ovbuff->freeblk == ovbuff->totalblk)
827     ovbuff->freeblk = ov.blocknum;
828   ovbuff->usedblk--;
829   ovbuff->needflush = true;
830   ovflushhead(ovbuff);
831   ovlock(ovbuff, INN_LOCK_UNLOCK);
832   return;
833 }
834
835 bool buffindexed_open(int mode) {
836   char          *groupfn;
837   struct stat   sb;
838   int           i, flag = 0;
839   static int    uninitialized = 1;
840   ULONG         on, off;
841
842   if (uninitialized) {
843     on = 1;
844     off = on;
845     off ^= ULONG_MAX;
846     for (i = (longsize * 8) - 1; i >= 0; i--) {
847       onarray[i] = on;
848       offarray[i] = off;
849       on <<= 1;
850       off = on;
851       off ^= ULONG_MAX;
852     }
853     uninitialized = 0;
854   }
855   ovbuffmode = mode;
856   if (pagesize == 0) {
857     pagesize = getpagesize();
858     if (pagesize == -1) {
859       syslog(L_ERROR, "%s: getpagesize failed: %m", LocalLogName);
860       pagesize = 0;
861       return false;
862     }
863     if ((pagesize > OV_HDR_PAGESIZE) || (OV_HDR_PAGESIZE % pagesize)) {
864       syslog(L_ERROR, "%s: OV_HDR_PAGESIZE (%d) is not a multiple of pagesize (%ld)", LocalLogName, OV_HDR_PAGESIZE, pagesize);
865       return false;
866     }
867   }
868   memset(&groupdatablock, '\0', sizeof(groupdatablock));
869   if (!ovbuffread_config()) {
870     return false;
871   }
872   Needunlink = true;
873   if (!ovbuffinit_disks()) {
874     return false;
875   }
876
877   groupfn = concatpath(innconf->pathdb, "group.index");
878   if (Needunlink && unlink(groupfn) == 0) {
879     syslog(L_NOTICE, "%s: all buffers are brandnew, unlink '%s'", LocalLogName, groupfn);
880   }
881   GROUPfd = open(groupfn, ovbuffmode & OV_WRITE ? O_RDWR | O_CREAT : O_RDONLY, 0660);
882   if (GROUPfd < 0) {
883     syslog(L_FATAL, "%s: Could not create %s: %m", LocalLogName, groupfn);
884     free(groupfn);
885     return false;
886   }
887
888   if (fstat(GROUPfd, &sb) < 0) {
889     syslog(L_FATAL, "%s: Could not fstat %s: %m", LocalLogName, groupfn);
890     free(groupfn);
891     close(GROUPfd);
892     return false;
893   }
894   if (sb.st_size > sizeof(GROUPHEADER)) {
895     if (mode & OV_READ)
896       flag |= PROT_READ;
897     if (mode & OV_WRITE) {
898       /*
899        * Note: below mapping of groupheader won't work unless we have
900        * both PROT_READ and PROT_WRITE perms.
901        */
902       flag |= PROT_WRITE|PROT_READ;
903     }
904     GROUPcount = (sb.st_size - sizeof(GROUPHEADER)) / sizeof(GROUPENTRY);
905     if ((GROUPheader = (GROUPHEADER *)mmap(0, GROUPfilesize(GROUPcount), flag,
906         MAP_SHARED, GROUPfd, 0)) == (GROUPHEADER *) -1) {
907       syslog(L_FATAL, "%s: Could not mmap %s in buffindexed_open: %m", LocalLogName, groupfn);
908       free(groupfn);
909       close(GROUPfd);
910       return false;
911     }
912     GROUPentries = (GROUPENTRY *)((char *)GROUPheader + sizeof(GROUPHEADER));
913   } else {
914     GROUPcount = 0;
915     if (!GROUPexpand(mode)) {
916       free(groupfn);
917       close(GROUPfd);
918       return false;
919     }
920   }
921   close_on_exec(GROUPfd, true);
922
923   free(groupfn);
924   Cutofflow = false;
925
926   return true;
927 }
928
929 static GROUPLOC GROUPfind(char *group, bool Ignoredeleted) {
930   HASH          grouphash;
931   unsigned int  i;
932   GROUPLOC      loc;
933
934   grouphash = Hash(group, strlen(group));
935   memcpy(&i, &grouphash, sizeof(i));
936
937   loc = GROUPheader->hash[i % GROUPHEADERHASHSIZE];
938   GROUPremapifneeded(loc);
939
940   while (!GROUPLOCempty(loc)) {
941     if (GROUPentries[loc.recno].deleted == 0 || Ignoredeleted) {
942       if (memcmp(&grouphash, &GROUPentries[loc.recno].hash, sizeof(HASH)) == 0) {
943         return loc;
944       }
945     }
946     loc = GROUPentries[loc.recno].next;
947   }
948   return GROUPemptyloc;
949 }
950
951 bool buffindexed_groupstats(char *group, int *lo, int *hi, int *count, int *flag) {
952   GROUPLOC      gloc;
953
954   gloc = GROUPfind(group, false);
955   if (GROUPLOCempty(gloc)) {
956     return false;
957   }
958   GROUPlock(gloc, INN_LOCK_READ);
959   if (lo != NULL)
960     *lo = GROUPentries[gloc.recno].low;
961   if (hi != NULL)
962     *hi = GROUPentries[gloc.recno].high;
963   if (count != NULL)
964     *count = GROUPentries[gloc.recno].count;
965   if (flag != NULL)
966     *flag = GROUPentries[gloc.recno].flag;
967   GROUPlock(gloc, INN_LOCK_UNLOCK);
968   return true;
969 }
970
971 static void setinitialge(GROUPENTRY *ge, HASH grouphash, char *flag, GROUPLOC next, ARTNUM lo, ARTNUM hi) {
972   ge->hash = grouphash;
973   if (lo != 0)
974     ge->low = lo;
975   ge->high = hi;
976   ge->expired = ge->deleted = ge->count = 0;
977   ge->flag = *flag;
978   ge->baseindex = ge->curindex = ge->curdata = ovnull;
979   ge->curindexoffset = ge->curoffset = 0;
980   ge->next = next;
981 }
982
983 bool buffindexed_groupadd(char *group, ARTNUM lo, ARTNUM hi, char *flag) {
984   unsigned int  i;
985   HASH          grouphash;
986   GROUPLOC      gloc;
987   GROUPENTRY    *ge;
988 #ifdef OV_DEBUG
989   struct ov_name_table  *ntp;
990 #endif /* OV_DEBUG */
991
992   gloc = GROUPfind(group, true);
993   if (!GROUPLOCempty(gloc)) {
994     ge = &GROUPentries[gloc.recno];
995     if (GROUPentries[gloc.recno].deleted != 0) {
996       grouphash = Hash(group, strlen(group));
997       setinitialge(ge, grouphash, flag, ge->next, lo, hi);
998     } else {
999       ge->flag = *flag;
1000     }
1001     return true;
1002   }
1003   grouphash = Hash(group, strlen(group));
1004   memcpy(&i, &grouphash, sizeof(i));
1005   i = i % GROUPHEADERHASHSIZE;
1006   GROUPlockhash(INN_LOCK_WRITE);
1007   gloc = GROUPnewnode();
1008   ge = &GROUPentries[gloc.recno];
1009   setinitialge(ge, grouphash, flag, GROUPheader->hash[i], lo, hi);
1010   GROUPheader->hash[i] = gloc;
1011 #ifdef OV_DEBUG
1012   ntp = xmalloc(sizeof(struct ov_name_table));
1013   memset(ntp, '\0', sizeof(struct ov_name_table));
1014   ntp->name = xstrdup(group);
1015   ntp->recno = gloc.recno;
1016   if (name_table == NULL)
1017     name_table = ntp;
1018   else {
1019     ntp->next = name_table;
1020     name_table = ntp;
1021   }
1022 #endif /* OV_DEBUG */
1023   GROUPlockhash(INN_LOCK_UNLOCK);
1024   return true;
1025 }
1026
1027 static off_t GROUPfilesize(int count) {
1028   return ((off_t) count * sizeof(GROUPENTRY)) + sizeof(GROUPHEADER);
1029 }
1030
1031 /* Check if the given GROUPLOC refers to GROUPENTRY that we don't have mmap'ed,
1032 ** if so then see if the file has been grown by another writer and remmap
1033 */
1034 static bool GROUPremapifneeded(GROUPLOC loc) {
1035   struct stat   sb;
1036
1037   if (loc.recno < GROUPcount)
1038     return true;
1039
1040   if (fstat(GROUPfd, &sb) < 0)
1041     return false;
1042
1043   if (GROUPfilesize(GROUPcount) >= sb.st_size)
1044     return true;
1045
1046   if (GROUPheader) {
1047     if (munmap((void *)GROUPheader, GROUPfilesize(GROUPcount)) < 0) {
1048       syslog(L_FATAL, "%s: Could not munmap group.index in GROUPremapifneeded: %m", LocalLogName);
1049       return false;
1050     }
1051   }
1052
1053   GROUPcount = (sb.st_size - sizeof(GROUPHEADER)) / sizeof(GROUPENTRY);
1054   GROUPheader = (GROUPHEADER *)mmap(0, GROUPfilesize(GROUPcount),
1055                                      PROT_READ | PROT_WRITE, MAP_SHARED, GROUPfd, 0);
1056   if (GROUPheader == (GROUPHEADER *) -1) {
1057     syslog(L_FATAL, "%s: Could not mmap group.index in GROUPremapifneeded: %m", LocalLogName);
1058     return false;
1059   }
1060   GROUPentries = (GROUPENTRY *)((char *)GROUPheader + sizeof(GROUPHEADER));
1061   return true;
1062 }
1063
1064 /* This function does not need to lock because it's callers are expected to do so */
1065 static bool GROUPexpand(int mode) {
1066   int   i;
1067   int   flag = 0;
1068
1069   if (GROUPheader) {
1070     if (munmap((void *)GROUPheader, GROUPfilesize(GROUPcount)) < 0) {
1071       syslog(L_FATAL, "%s: Could not munmap group.index in GROUPexpand: %m", LocalLogName);
1072       return false;
1073     }
1074   }
1075   GROUPcount += 1024;
1076   if (ftruncate(GROUPfd, GROUPfilesize(GROUPcount)) < 0) {
1077     syslog(L_FATAL, "%s: Could not extend group.index: %m", LocalLogName);
1078     return false;
1079   }
1080   if (mode & OV_READ)
1081     flag |= PROT_READ;
1082   if (mode & OV_WRITE) {
1083     /*
1084      * Note: below check of magic won't work unless we have both PROT_READ
1085      * and PROT_WRITE perms.
1086      */
1087     flag |= PROT_WRITE|PROT_READ;
1088   }
1089   GROUPheader = (GROUPHEADER *)mmap(0, GROUPfilesize(GROUPcount),
1090                                      flag, MAP_SHARED, GROUPfd, 0);
1091   if (GROUPheader == (GROUPHEADER *) -1) {
1092     syslog(L_FATAL, "%s: Could not mmap group.index in GROUPexpand: %m", LocalLogName);
1093     return false;
1094   }
1095   GROUPentries = (GROUPENTRY *)((char *)GROUPheader + sizeof(GROUPHEADER));
1096   if (GROUPheader->magic != GROUPHEADERMAGIC) {
1097     GROUPheader->magic = GROUPHEADERMAGIC;
1098     GROUPLOCclear(&GROUPheader->freelist);
1099     for (i = 0; i < GROUPHEADERHASHSIZE; i++)
1100       GROUPLOCclear(&GROUPheader->hash[i]);
1101   }
1102   /* Walk the new entries from the back to the front, adding them to the freelist */
1103   for (i = GROUPcount - 1; (GROUPcount - 1024) <= i; i--) {
1104     GROUPentries[i].next = GROUPheader->freelist;
1105     GROUPheader->freelist.recno = i;
1106   }
1107   return true;
1108 }
1109
1110 static GROUPLOC GROUPnewnode(void) {
1111   GROUPLOC      loc;
1112
1113   /* If we didn't find any free space, then make some */
1114   if (GROUPLOCempty(GROUPheader->freelist)) {
1115     if (!GROUPexpand(ovbuffmode)) {
1116       return GROUPemptyloc;
1117     }
1118   }
1119   assert(!GROUPLOCempty(GROUPheader->freelist));
1120   loc = GROUPheader->freelist;
1121   GROUPheader->freelist = GROUPentries[GROUPheader->freelist.recno].next;
1122   return loc;
1123 }
1124
1125 bool buffindexed_groupdel(char *group) {
1126   GROUPLOC      gloc;
1127   GROUPENTRY    *ge;
1128
1129   gloc = GROUPfind(group, false);
1130   if (GROUPLOCempty(gloc)) {
1131     return true;
1132   }
1133   GROUPlock(gloc, INN_LOCK_WRITE);
1134   ge = &GROUPentries[gloc.recno];
1135   ge->deleted = time(NULL);
1136   HashClear(&ge->hash);
1137   GROUPlock(gloc, INN_LOCK_UNLOCK);
1138   return true;
1139 }
1140
1141 static void GROUPLOCclear(GROUPLOC *loc) {
1142   loc->recno = -1;
1143 }
1144
1145 static bool GROUPLOCempty(GROUPLOC loc) {
1146   return (loc.recno < 0);
1147 }
1148
1149 static bool GROUPlockhash(enum inn_locktype type) {
1150   return inn_lock_range(GROUPfd, type, true, 0, sizeof(GROUPHEADER));
1151 }
1152
1153 static bool GROUPlock(GROUPLOC gloc, enum inn_locktype type) {
1154   return inn_lock_range(GROUPfd,
1155              type,
1156              true,
1157              sizeof(GROUPHEADER) + (sizeof(GROUPENTRY) * gloc.recno),
1158              sizeof(GROUPENTRY));
1159 }
1160
1161 #ifdef OV_DEBUG
1162 static bool ovsetcurindexblock(GROUPENTRY *ge, GROUPENTRY *georig) {
1163 #else
1164 static bool ovsetcurindexblock(GROUPENTRY *ge) {
1165 #endif /* OV_DEBUG */
1166   OVBUFF        *ovbuff;
1167   OV            ov;
1168   OVINDEXHEAD   ovindexhead;
1169
1170   /* there is no index */
1171 #ifdef OV_DEBUG
1172   ov = ovblocknew(georig ? georig : ge);
1173 #else
1174   ov = ovblocknew();
1175 #endif /* OV_DEBUG */
1176   if (ov.index == NULLINDEX) {
1177     syslog(L_ERROR, "%s: ovsetcurindexblock could not get new block", LocalLogName);
1178     return false;
1179   }
1180   if ((ovbuff = getovbuff(ov)) == NULL) {
1181     syslog(L_ERROR, "%s: ovsetcurindexblock could not get ovbuff block for new, %d, %d", LocalLogName, ov.index, ov.blocknum);
1182     return false;
1183   }
1184   ovindexhead.next = ovnull;
1185   ovindexhead.low = 0;
1186   ovindexhead.high = 0;
1187 #ifdef MMAP_MISSES_WRITES
1188   if (mmapwrite(ovbuff->fd, &ovindexhead, sizeof(OVINDEXHEAD), ovbuff->base + ov.blocknum * OV_BLOCKSIZE) != sizeof(OVINDEXHEAD)) {
1189 #else
1190   if (pwrite(ovbuff->fd, &ovindexhead, sizeof(OVINDEXHEAD), ovbuff->base + ov.blocknum * OV_BLOCKSIZE) != sizeof(OVINDEXHEAD)) {
1191 #endif /* MMAP_MISSES_WRITES */
1192     syslog(L_ERROR, "%s: could not write index record index '%d', blocknum '%d': %m", LocalLogName, ge->curindex.index, ge->curindex.blocknum);
1193     return true;
1194   }
1195   if (ge->baseindex.index == NULLINDEX) {
1196     ge->baseindex = ov;
1197   } else {
1198     if ((ovbuff = getovbuff(ge->curindex)) == NULL)
1199       return false;
1200 #ifdef OV_DEBUG
1201     if (!ovusedblock(ovbuff, ge->curindex.blocknum, false, false)) {
1202       syslog(L_FATAL, "%s: block(%d, %d) not occupied (index)", LocalLogName, ovbuff->index, ge->curindex.blocknum);
1203       abort();
1204     }
1205 #endif /* OV_DEBUG */
1206     ovindexhead.next = ov;
1207     ovindexhead.low = ge->curlow;
1208     ovindexhead.high = ge->curhigh;
1209 #ifdef MMAP_MISSES_WRITES
1210     if (mmapwrite(ovbuff->fd, &ovindexhead, sizeof(OVINDEXHEAD), ovbuff->base + ge->curindex.blocknum * OV_BLOCKSIZE) != sizeof(OVINDEXHEAD)) {
1211 #else
1212     if (pwrite(ovbuff->fd, &ovindexhead, sizeof(OVINDEXHEAD), ovbuff->base + ge->curindex.blocknum * OV_BLOCKSIZE) != sizeof(OVINDEXHEAD)) {
1213 #endif /* MMAP_MISSES_WRITES */
1214       syslog(L_ERROR, "%s: could not write index record index '%d', blocknum '%d': %m", LocalLogName, ge->curindex.index, ge->curindex.blocknum);
1215       return false;
1216     }
1217   }
1218   ge->curindex = ov;
1219   ge->curindexoffset = 0;
1220   ge->curlow = 0;
1221   ge->curhigh = 0;
1222   return true;
1223 }
1224
1225 #ifdef OV_DEBUG
1226 static bool ovaddrec(GROUPENTRY *ge, ARTNUM artnum, TOKEN token, char *data, int len, time_t arrived, time_t expires, GROUPENTRY *georig) {
1227 #else
1228 static bool ovaddrec(GROUPENTRY *ge, ARTNUM artnum, TOKEN token, char *data, int len, time_t arrived, time_t expires) {
1229 #endif /* OV_DEBUG */
1230   OV            ov;
1231   OVINDEX       ie;
1232   OVBUFF        *ovbuff;
1233   OVINDEXHEAD   ovindexhead;
1234   bool          needupdate = false;
1235 #ifdef OV_DEBUG
1236   int           recno;
1237 #endif /* OV_DEBUG */
1238
1239   Nospace = false;
1240   if (OV_BLOCKSIZE < len) {
1241     syslog(L_ERROR, "%s: overview data must be under %d (%d)", LocalLogName, OV_BLOCKSIZE, len);
1242     return false;
1243   }
1244   if (ge->curdata.index == NULLINDEX) {
1245     /* no data block allocated */
1246 #ifdef OV_DEBUG
1247     ov = ovblocknew(georig ? georig : ge);
1248 #else
1249     ov = ovblocknew();
1250 #endif /* OV_DEBUG */
1251     if (ov.index == NULLINDEX) {
1252       syslog(L_ERROR, "%s: ovaddrec could not get new block", LocalLogName);
1253       return false;
1254     }
1255     if ((ovbuff = getovbuff(ov)) == NULL) {
1256       syslog(L_ERROR, "%s: ovaddrec could not get ovbuff block for new, %d, %d, %ld", LocalLogName, ov.index, ov.blocknum, artnum);
1257       return false;
1258     }
1259     ge->curdata = ov;
1260     ge->curoffset = 0;
1261   } else if ((ovbuff = getovbuff(ge->curdata)) == NULL)
1262     return false;
1263   else if (OV_BLOCKSIZE - ge->curoffset < len) {
1264     /* too short to store data, allocate new block */
1265 #ifdef OV_DEBUG
1266     ov = ovblocknew(georig ? georig : ge);
1267 #else
1268     ov = ovblocknew();
1269 #endif /* OV_DEBUG */
1270     if (ov.index == NULLINDEX) {
1271       syslog(L_ERROR, "%s: ovaddrec could not get new block", LocalLogName);
1272       return false;
1273     }
1274     if ((ovbuff = getovbuff(ov)) == NULL) {
1275       syslog(L_ERROR, "%s: ovaddrec could not get ovbuff block for new, %d, %d, %ld", LocalLogName, ov.index, ov.blocknum, artnum);
1276       return false;
1277     }
1278     ge->curdata = ov;
1279     ge->curoffset = 0;
1280   }
1281 #ifdef OV_DEBUG
1282   if (!ovusedblock(ovbuff, ge->curdata.blocknum, false, false)) {
1283     syslog(L_FATAL, "%s: block(%d, %d) not occupied", LocalLogName, ovbuff->index, ge->curdata.blocknum);
1284     buffindexed_close();
1285     abort();
1286   }
1287 #endif /* OV_DEBUG */
1288 #ifdef MMAP_MISSES_WRITES
1289   if (mmapwrite(ovbuff->fd, data, len, ovbuff->base + ge->curdata.blocknum * OV_BLOCKSIZE + ge->curoffset) != len) {
1290 #else
1291   if (pwrite(ovbuff->fd, data, len, ovbuff->base + ge->curdata.blocknum * OV_BLOCKSIZE + ge->curoffset) != len) {
1292 #endif /* MMAP_MISSES_WRITES */
1293     syslog(L_ERROR, "%s: could not append overview record index '%d', blocknum '%d': %m", LocalLogName, ge->curdata.index, ge->curdata.blocknum);
1294     return false;
1295   }
1296   memset(&ie, '\0', sizeof(ie));
1297   ie.artnum = artnum;
1298   ie.len = len;
1299   ie.index = ge->curdata.index;
1300   ie.blocknum = ge->curdata.blocknum;
1301   ie.offset = ge->curoffset;
1302   ie.token = token;
1303   ie.arrived = arrived;
1304   ie.expires = expires;
1305
1306   if (ge->baseindex.index == NULLINDEX || ge->curindexoffset == OVINDEXMAX) {
1307 #ifdef OV_DEBUG
1308     if (!ovsetcurindexblock(ge, georig)) {
1309 #else
1310     if (!ovsetcurindexblock(ge)) {
1311 #endif /* OV_DEBUG */
1312       syslog(L_ERROR, "%s: could not set current index", LocalLogName);
1313       return false;
1314     }
1315   }
1316   if ((ovbuff = getovbuff(ge->curindex)) == NULL)
1317     return false;
1318 #ifdef OV_DEBUG
1319   if (!ovusedblock(ovbuff, ge->curindex.blocknum, false, false)) {
1320     syslog(L_FATAL, "%s: block(%d, %d) not occupied (index)", LocalLogName, ovbuff->index, ge->curindex.blocknum);
1321     buffindexed_close();
1322     abort();
1323   }
1324 #endif /* OV_DEBUG */
1325 #ifdef MMAP_MISSES_WRITES
1326   if (mmapwrite(ovbuff->fd, &ie, sizeof(ie), ovbuff->base + ge->curindex.blocknum * OV_BLOCKSIZE + sizeof(OVINDEXHEAD) + sizeof(ie) * ge->curindexoffset) != sizeof(ie)) {
1327 #else
1328   if (pwrite(ovbuff->fd, &ie, sizeof(ie), ovbuff->base + ge->curindex.blocknum * OV_BLOCKSIZE + sizeof(OVINDEXHEAD) + sizeof(ie) * ge->curindexoffset) != sizeof(ie)) {
1329 #endif /* MMAP_MISSES_WRITES */
1330     syslog(L_ERROR, "%s: could not write index record index '%d', blocknum '%d': %m", LocalLogName, ge->curindex.index, ge->curindex.blocknum);
1331     return true;
1332   }
1333   if ((ge->curlow <= 0) || (ge->curlow > artnum)) {
1334     ge->curlow = artnum;
1335     needupdate = true;
1336   }
1337   if ((ge->curhigh <= 0) || (ge->curhigh < artnum)) {
1338     ge->curhigh = artnum;
1339     needupdate = true;
1340   }
1341   if (needupdate) {
1342     ovindexhead.next = ovnull;
1343     ovindexhead.low = ge->curlow;
1344     ovindexhead.high = ge->curhigh;
1345 #ifdef MMAP_MISSES_WRITES
1346     if (mmapwrite(ovbuff->fd, &ovindexhead, sizeof(OVINDEXHEAD), ovbuff->base + ge->curindex.blocknum * OV_BLOCKSIZE) != sizeof(OVINDEXHEAD)) {
1347 #else
1348     if (pwrite(ovbuff->fd, &ovindexhead, sizeof(OVINDEXHEAD), ovbuff->base + ge->curindex.blocknum * OV_BLOCKSIZE) != sizeof(OVINDEXHEAD)) {
1349 #endif /* MMAP_MISSES_WRITES */
1350       syslog(L_ERROR, "%s: could not write index record index '%d', blocknum '%d': %m", LocalLogName, ge->curindex.index, ge->curindex.blocknum);
1351       return true;
1352     }
1353   }
1354   if ((ge->low <= 0) || (ge->low > artnum))
1355     ge->low = artnum;
1356   if ((ge->high <= 0) || (ge->high < artnum))
1357     ge->high = artnum;
1358   ge->curindexoffset++;
1359   ge->curoffset += len;
1360   ge->count++;
1361   return true;
1362 }
1363
1364 bool buffindexed_add(char *group, ARTNUM artnum, TOKEN token, char *data, int len, time_t arrived, time_t expires) {
1365   GROUPLOC      gloc;
1366   GROUPENTRY    *ge;
1367
1368   if (len > OV_BLOCKSIZE) {
1369     syslog(L_ERROR, "%s: overview data is too large %d", LocalLogName, len);
1370     return true;
1371   }
1372
1373   gloc = GROUPfind(group, false);
1374   if (GROUPLOCempty(gloc)) {
1375     return true;
1376   }
1377   GROUPlock(gloc, INN_LOCK_WRITE);
1378   /* prepend block(s) if needed. */
1379   ge = &GROUPentries[gloc.recno];
1380   if (Cutofflow && ge->low > artnum) {
1381     GROUPlock(gloc, INN_LOCK_UNLOCK);
1382     return true;
1383   }
1384 #ifdef OV_DEBUG
1385   if (!ovaddrec(ge, artnum, token, data, len, arrived, expires, NULL)) {
1386 #else
1387   if (!ovaddrec(ge, artnum, token, data, len, arrived, expires)) {
1388 #endif /* OV_DEBUG */
1389     if (Nospace) {
1390       GROUPlock(gloc, INN_LOCK_UNLOCK);
1391       syslog(L_ERROR, "%s: no space left for buffer, adding '%s'", LocalLogName, group);
1392       return false;
1393     }
1394     syslog(L_ERROR, "%s: could not add overview for '%s'", LocalLogName, group);
1395   }
1396   GROUPlock(gloc, INN_LOCK_UNLOCK);
1397
1398   return true;
1399 }
1400
1401 bool buffindexed_cancel(TOKEN token UNUSED) {
1402     return true;
1403 }
1404
1405 #ifdef OV_DEBUG
1406 static void freegroupblock(GROUPENTRY *ge) {
1407 #else
1408 static void freegroupblock(void) {
1409 #endif /* OV_DEBUG */
1410   GROUPDATABLOCK        *gdb;
1411   int                   i;
1412   GIBLIST               *giblist;
1413
1414   for (giblist = Giblist ; giblist != NULL ; giblist = giblist->next) {
1415 #ifdef OV_DEBUG
1416     ovblockfree(giblist->ov, ge);
1417 #else
1418     ovblockfree(giblist->ov);
1419 #endif /* OV_DEBUG */
1420   }
1421   for (i = 0 ; i < GROUPDATAHASHSIZE ; i++) {
1422     for (gdb = groupdatablock[i] ; gdb != NULL ; gdb = gdb->next) {
1423 #ifdef OV_DEBUG
1424       ovblockfree(gdb->datablk, ge);
1425 #else
1426       ovblockfree(gdb->datablk);
1427 #endif /* OV_DEBUG */
1428     }
1429   }
1430 }
1431
1432 static void ovgroupunmap(void) {
1433   GROUPDATABLOCK        *gdb, *gdbnext;
1434   int                   i;
1435   GIBLIST               *giblist, *giblistnext;
1436
1437   for (i = 0 ; i < GROUPDATAHASHSIZE ; i++) {
1438     for (gdb = groupdatablock[i] ; gdb != NULL ; gdb = gdbnext) {
1439       gdbnext = gdb->next;
1440       free(gdb);
1441     }
1442     groupdatablock[i] = NULL;
1443   }
1444   for (giblist = Giblist ; giblist != NULL ; giblist = giblistnext) {
1445     giblistnext = giblist->next;
1446     free(giblist);
1447   }
1448   Giblist = NULL;
1449   if (!Cache && (Gib != NULL)) {
1450     free(Gib);
1451     Gib = NULL;
1452     if (Cachesearch != NULL) {
1453       free(Cachesearch->group);
1454       free(Cachesearch);
1455       Cachesearch = NULL;
1456     }
1457   }
1458 }
1459
1460 static void insertgdb(OV *ov, GROUPDATABLOCK *gdb) {
1461   gdb->next = groupdatablock[(ov->index + ov->blocknum) % GROUPDATAHASHSIZE];
1462   groupdatablock[(ov->index + ov->blocknum) % GROUPDATAHASHSIZE] = gdb;
1463   return;
1464 }
1465
1466 static GROUPDATABLOCK *searchgdb(OV *ov) {
1467   GROUPDATABLOCK        *gdb;
1468
1469   gdb = groupdatablock[(ov->index + ov->blocknum) % GROUPDATAHASHSIZE];
1470   for (; gdb != NULL ; gdb = gdb->next) {
1471     if (ov->index == gdb->datablk.index && ov->blocknum == gdb->datablk.blocknum)
1472       break;
1473   }
1474   return gdb;
1475 }
1476
1477 static int INDEXcompare(const void *p1, const void *p2) {
1478   OVINDEX       *oi1;
1479   OVINDEX       *oi2;
1480  
1481   oi1 = (OVINDEX *)p1;
1482   oi2 = (OVINDEX *)p2;  
1483   return oi1->artnum - oi2->artnum;
1484 }
1485
1486 static bool ovgroupmmap(GROUPENTRY *ge, int low, int high, bool needov) {
1487   OV                    ov = ge->baseindex;
1488   OVBUFF                *ovbuff;
1489   GROUPDATABLOCK        *gdb;
1490   int                   pagefudge, limit, i, count, len;
1491   off_t                 offset, mmapoffset;
1492   OVBLOCK               *ovblock;
1493   void *                addr;
1494   GIBLIST               *giblist;
1495
1496   if (high - low < 0) {
1497     Gibcount = 0;
1498     return true;
1499   }
1500   Gibcount = ge->count;
1501   if (Gibcount == 0)
1502     return true;
1503   Gib = xmalloc(Gibcount * sizeof(OVINDEX));
1504   count = 0;
1505   while (ov.index != NULLINDEX) {
1506     ovbuff = getovbuff(ov);
1507     if (ovbuff == NULL) {
1508       syslog(L_ERROR, "%s: ovgroupmmap ovbuff is null(ovindex is %d, ovblock is %d", LocalLogName, ov.index, ov.blocknum);
1509       ovgroupunmap();
1510       return false;
1511     }
1512     offset = ovbuff->base + (ov.blocknum * OV_BLOCKSIZE);
1513     pagefudge = offset % pagesize;
1514     mmapoffset = offset - pagefudge;
1515     len = pagefudge + OV_BLOCKSIZE;
1516     if ((addr = mmap(NULL, len, PROT_READ, MAP_SHARED, ovbuff->fd, mmapoffset)) == MAP_FAILED) {
1517       syslog(L_ERROR, "%s: ovgroupmmap could not mmap index block: %m", LocalLogName);
1518       ovgroupunmap();
1519       return false;
1520     }
1521     ovblock = (OVBLOCK *)((char *)addr + pagefudge);
1522     if (ov.index == ge->curindex.index && ov.blocknum == ge->curindex.blocknum) {
1523       limit = ge->curindexoffset;
1524     } else {
1525       limit = OVINDEXMAX;
1526     }
1527     for (i = 0 ; i < limit ; i++) {
1528       if (Gibcount == count) {
1529         Gibcount += OV_FUDGE;
1530         Gib = xrealloc(Gib, Gibcount * sizeof(OVINDEX));
1531       }
1532       Gib[count++] = ovblock->ovindex[i];
1533     }
1534     giblist = xmalloc(sizeof(GIBLIST));
1535     giblist->ov = ov;
1536     giblist->next = Giblist;
1537     Giblist = giblist;
1538     ov = ovblock->ovindexhead.next;
1539     munmap(addr, len);
1540   }
1541   Gibcount = count;
1542   qsort(Gib, Gibcount, sizeof(OVINDEX), INDEXcompare);
1543   /* Remove duplicates. */
1544   for (i = 0; i < Gibcount - 1; i++) {
1545     if (Gib[i].artnum == Gib[i+1].artnum) {
1546       /* lower position is removed */
1547       Gib[i].artnum = 0;
1548     }
1549   }
1550   if (!needov)
1551     return true;
1552   count = 0;
1553   for (i = 0 ; i < Gibcount ; i++) {
1554     if (Gib[i].artnum == 0 || Gib[i].artnum < low || Gib[i].artnum > high)
1555       continue;
1556     ov.index = Gib[i].index;
1557     ov.blocknum = Gib[i].blocknum;
1558     gdb = searchgdb(&ov);
1559     if (gdb != NULL)
1560       continue;
1561     ovbuff = getovbuff(ov);
1562     if (ovbuff == NULL)
1563       continue;
1564     gdb = xmalloc(sizeof(GROUPDATABLOCK));
1565     gdb->datablk = ov;
1566     gdb->next = NULL;
1567     gdb->mmapped = false;
1568     insertgdb(&ov, gdb);
1569     count++;
1570   }
1571   if (count == 0)
1572     return true;
1573   if (count * OV_BLOCKSIZE > innconf->keepmmappedthreshold * 1024)
1574     /* large retrieval, mmap is done in ovsearch() */
1575     return true;
1576   for (i = 0 ; i < GROUPDATAHASHSIZE ; i++) {
1577     for (gdb = groupdatablock[i] ; gdb != NULL ; gdb = gdb->next) {
1578       ov = gdb->datablk;
1579       ovbuff = getovbuff(ov);
1580       offset = ovbuff->base + (ov.blocknum * OV_BLOCKSIZE);
1581       pagefudge = offset % pagesize;
1582       mmapoffset = offset - pagefudge;
1583       gdb->len = pagefudge + OV_BLOCKSIZE;
1584       if ((gdb->addr = mmap(NULL, gdb->len, PROT_READ, MAP_SHARED, ovbuff->fd, mmapoffset)) == MAP_FAILED) {
1585         syslog(L_ERROR, "%s: ovgroupmmap could not mmap data block: %m", LocalLogName);
1586         free(gdb);
1587         ovgroupunmap();
1588         return false;
1589       }
1590       gdb->data = (char *)gdb->addr + pagefudge;
1591       gdb->mmapped = true;
1592     }
1593   }
1594   return true;
1595 }
1596
1597 static void *ovopensearch(char *group, int low, int high, bool needov) {
1598   GROUPLOC              gloc;
1599   GROUPENTRY            *ge;
1600   OVSEARCH              *search;
1601
1602   gloc = GROUPfind(group, false);
1603   if (GROUPLOCempty(gloc))
1604     return NULL;
1605
1606   ge = &GROUPentries[gloc.recno];
1607   if (low < ge->low)
1608     low = ge->low;
1609   if (high > ge->high)
1610     high = ge->high;
1611
1612   if (!ovgroupmmap(ge, low, high, needov)) {
1613     return NULL;
1614   }
1615
1616   search = xmalloc(sizeof(OVSEARCH));
1617   search->hi = high;
1618   search->lo = low;
1619   search->cur = 0;
1620   search->group = xstrdup(group);
1621   search->needov = needov;
1622   search->gloc = gloc;
1623   search->count = ge->count;
1624   search->gdb.mmapped = false;
1625   return (void *)search;
1626 }
1627
1628 void *buffindexed_opensearch(char *group, int low, int high) {
1629   GROUPLOC              gloc;
1630   void                  *handle;
1631
1632   if (Gib != NULL) {
1633     free(Gib);
1634     Gib = NULL;
1635     if (Cachesearch != NULL) {
1636       free(Cachesearch->group);
1637       free(Cachesearch);
1638       Cachesearch = NULL;
1639     }
1640   }
1641   gloc = GROUPfind(group, false);
1642   if (GROUPLOCempty(gloc)) {
1643     return NULL;
1644   }
1645   GROUPlock(gloc, INN_LOCK_WRITE);
1646   if ((handle = ovopensearch(group, low, high, true)) == NULL)
1647     GROUPlock(gloc, INN_LOCK_UNLOCK);
1648   return(handle);
1649 }
1650
1651 static bool ovsearch(void *handle, ARTNUM *artnum, char **data, int *len, TOKEN *token, time_t *arrived, time_t *expires) {
1652   OVSEARCH              *search = (OVSEARCH *)handle;
1653   OV                    srchov;
1654   GROUPDATABLOCK        *gdb;
1655   off_t                 offset, mmapoffset;
1656   OVBUFF                *ovbuff;
1657   int                   pagefudge;
1658   bool                  newblock;
1659
1660   if (search->cur == Gibcount) {
1661     return false;
1662   }
1663   while (Gib[search->cur].artnum == 0 || Gib[search->cur].artnum < search->lo) {
1664     search->cur++;
1665     if (search->cur == Gibcount)
1666       return false;
1667   }
1668   if (Gib[search->cur].artnum > search->hi)
1669       return false;
1670
1671   if (search->needov) {
1672     if (Gib[search->cur].index == NULLINDEX) {
1673       if (len)
1674         *len = 0;
1675       if (artnum)
1676         *artnum = Gib[search->cur].artnum;
1677     } else {
1678       if (artnum)
1679         *artnum = Gib[search->cur].artnum;
1680       if (len)
1681         *len = Gib[search->cur].len;
1682       if (arrived)
1683         *arrived = Gib[search->cur].arrived;
1684       if (expires)
1685         *expires = Gib[search->cur].expires;
1686       if (data) {
1687         srchov.index = Gib[search->cur].index;
1688         srchov.blocknum = Gib[search->cur].blocknum;
1689         gdb = searchgdb(&srchov);
1690         if (gdb == NULL) {
1691           if (len)
1692             *len = 0;
1693           search->cur++;
1694           return true;
1695         }
1696         if (!gdb->mmapped) {
1697           /* block needs to be mmapped */
1698           if (search->gdb.mmapped) {
1699             /* check previous mmapped area */
1700             if (search->gdb.datablk.blocknum != srchov.blocknum || search->gdb.datablk.index != srchov.index) {
1701               /* different one, release previous one */
1702               munmap(search->gdb.addr, search->gdb.len);
1703               newblock = true;
1704             } else
1705               newblock = false;
1706           } else
1707             newblock = true;
1708           if (newblock) {
1709             search->gdb.datablk.blocknum = srchov.blocknum;
1710             search->gdb.datablk.index = srchov.index;
1711             ovbuff = getovbuff(srchov);
1712             offset = ovbuff->base + (srchov.blocknum * OV_BLOCKSIZE);
1713             pagefudge = offset % pagesize;
1714             mmapoffset = offset - pagefudge;
1715             search->gdb.len = pagefudge + OV_BLOCKSIZE;
1716             if ((search->gdb.addr = mmap(NULL, search->gdb.len, PROT_READ, MAP_SHARED, ovbuff->fd, mmapoffset)) == MAP_FAILED) {
1717               syslog(L_ERROR, "%s: ovsearch could not mmap data block: %m", LocalLogName);
1718               return false;
1719             }
1720             gdb->data = search->gdb.data = (char *)search->gdb.addr + pagefudge;
1721             search->gdb.mmapped = true;
1722           }
1723         }
1724         *data = (char *)gdb->data + Gib[search->cur].offset;
1725       }
1726     }
1727   }
1728   if (token) {
1729     if (Gib[search->cur].index == NULLINDEX && !search->needov) {
1730       search->cur++;
1731       return false;
1732     }
1733     *token = Gib[search->cur].token;
1734   }
1735   search->cur++;
1736   return true;
1737 }
1738
1739 bool buffindexed_search(void *handle, ARTNUM *artnum, char **data, int *len, TOKEN *token, time_t *arrived) {
1740   return(ovsearch(handle, artnum, data, len, token, arrived, NULL));
1741 }
1742
1743 static void ovclosesearch(void *handle, bool freeblock) {
1744   OVSEARCH              *search = (OVSEARCH *)handle;
1745   GROUPDATABLOCK        *gdb;
1746   int                   i;
1747 #ifdef OV_DEBUG
1748   GROUPENTRY    *ge;
1749   GROUPLOC      gloc;
1750 #endif /* OV_DEBUG */
1751
1752   for (i = 0 ; i < GROUPDATAHASHSIZE ; i++) {
1753     for (gdb = groupdatablock[i] ; gdb != NULL ; gdb = gdb->next) {
1754       if (gdb->mmapped)
1755         munmap(gdb->addr, gdb->len);
1756     }
1757   }
1758   if (search->gdb.mmapped)
1759     munmap(search->gdb.addr, search->gdb.len);
1760   if (freeblock) {
1761 #ifdef OV_DEBUG
1762     gloc = GROUPfind(search->group, false);
1763     if (!GROUPLOCempty(gloc)) {
1764       ge = &GROUPentries[gloc.recno];
1765       freegroupblock(ge);
1766     }
1767 #else
1768     freegroupblock();
1769 #endif /* OV_DEBUG */
1770   }
1771   ovgroupunmap();
1772   if (Cache) {
1773     Cachesearch = search;
1774   } else {
1775     free(search->group);
1776     free(search);
1777   }
1778   return;
1779 }
1780
1781 void buffindexed_closesearch(void *handle) {
1782   OVSEARCH      *search = (OVSEARCH *)handle;
1783   GROUPLOC      gloc;
1784
1785   gloc = search->gloc;
1786   ovclosesearch(handle, false);
1787   GROUPlock(gloc, INN_LOCK_UNLOCK);
1788 }
1789
1790 /* get token from sorted index */
1791 static bool gettoken(ARTNUM artnum, TOKEN *token) {
1792   int   i, j, offset, limit;
1793   offset = 0;
1794   limit = Gibcount;
1795   for (i = (limit - offset) / 2 ; i > 0 ; i = (limit - offset) / 2) {
1796     if (Gib[offset + i].artnum == artnum) {
1797       *token = Gib[offset + i].token;
1798       return true;
1799     } else if (Gib[offset + i].artnum == 0) {
1800       /* case for duplicated index */
1801       for (j = offset + i - 1; j >= offset ; j --) {
1802         if (Gib[j].artnum != 0)
1803           break;
1804       }
1805       if (j < offset) {
1806         /* article not found */
1807         return false;
1808       }
1809       if (Gib[j].artnum == artnum) {
1810         *token = Gib[j].token;
1811         return true;
1812       } else if (Gib[j].artnum < artnum) {
1813         /* limit is not changed */
1814         offset += i + 1;
1815       } else {
1816         /* offset is not changed */
1817         limit = j;
1818       }
1819     } else if (Gib[offset + i].artnum < artnum) {
1820       /* limit is unchanged */
1821       offset += i + 1;
1822     } else {
1823       /* offset is unchanged */
1824       limit = offset + i;
1825     }
1826   }
1827   /* i == 0 */
1828   if (Gib[offset].artnum != artnum) {
1829     /* article not found */
1830     return false;
1831   }
1832   *token = Gib[offset].token;
1833   return true;
1834 }
1835
1836 bool buffindexed_getartinfo(char *group, ARTNUM artnum, TOKEN *token) {
1837   GROUPLOC      gloc;
1838   void          *handle;
1839   bool          retval, grouplocked = false;
1840
1841   if (Gib != NULL) {
1842     if (Cachesearch != NULL && strcmp(Cachesearch->group, group) != 0) {
1843       free(Gib);
1844       Gib = NULL;
1845       free(Cachesearch->group);
1846       free(Cachesearch);
1847       Cachesearch = NULL;
1848     } else {
1849       if (gettoken(artnum, token))
1850         return true;
1851       else {
1852         /* examine to see if overview index are increased */
1853         gloc = GROUPfind(group, false);
1854         if (GROUPLOCempty(gloc)) {
1855           return false;
1856         }
1857         GROUPlock(gloc, INN_LOCK_WRITE);
1858         if ((Cachesearch != NULL) && (GROUPentries[gloc.recno].count == Cachesearch->count)) {
1859           /* no new overview data is stored */
1860           GROUPlock(gloc, INN_LOCK_UNLOCK);
1861           return false;
1862         } else {
1863           grouplocked = true;
1864           free(Gib);
1865           Gib = NULL;
1866           if (Cachesearch != NULL) {
1867             free(Cachesearch->group);
1868             free(Cachesearch);
1869             Cachesearch = NULL;
1870           }
1871         }
1872       }
1873     }
1874   }
1875   if (!grouplocked) {
1876     gloc = GROUPfind(group, false);
1877     if (GROUPLOCempty(gloc)) {
1878       return false;
1879     }
1880     GROUPlock(gloc, INN_LOCK_WRITE);
1881   }
1882   if (!(handle = ovopensearch(group, artnum, artnum, false))) {
1883     GROUPlock(gloc, INN_LOCK_UNLOCK);
1884     return false;
1885   }
1886   retval = buffindexed_search(handle, NULL, NULL, NULL, token, NULL);
1887   ovclosesearch(handle, false);
1888   GROUPlock(gloc, INN_LOCK_UNLOCK);
1889   return retval;
1890 }
1891
1892 bool buffindexed_expiregroup(char *group, int *lo, struct history *h) {
1893   void          *handle;
1894   GROUPENTRY    newge, *ge;
1895   GROUPLOC      gloc, next;
1896   char          *data;
1897   int           i, j, len;
1898   TOKEN         token;
1899   ARTNUM        artnum, low, high;
1900   ARTHANDLE     *ah;
1901   char          flag;
1902   HASH          hash;
1903   time_t        arrived, expires;
1904
1905   if (group == NULL) {
1906     for (i = 0 ; i < GROUPheader->freelist.recno ; i++) {
1907       gloc.recno = i;
1908       GROUPlock(gloc, INN_LOCK_WRITE);
1909       ge = &GROUPentries[gloc.recno];
1910       if (ge->expired >= OVrealnow || ge->count == 0) {
1911         GROUPlock(gloc, INN_LOCK_UNLOCK);
1912         continue;
1913       }
1914       if (!ovgroupmmap(ge, ge->low, ge->high, true)) {
1915         GROUPlock(gloc, INN_LOCK_UNLOCK);
1916         syslog(L_ERROR, "%s: could not mmap overview for hidden groups(%d)", LocalLogName, i);
1917         continue;
1918       }
1919       for (j = 0 ; j < Gibcount ; j++) {
1920         if (Gib[j].artnum == 0)
1921           continue;
1922         /* this may be duplicated, but ignore it in this case */
1923         OVEXPremove(Gib[j].token, true, NULL, 0);
1924       }
1925 #ifdef OV_DEBUG
1926       freegroupblock(ge);
1927 #else
1928       freegroupblock();
1929 #endif
1930       ovgroupunmap();
1931       ge->expired = time(NULL);
1932       ge->count = 0;
1933       GROUPlock(gloc, INN_LOCK_UNLOCK);
1934     }
1935     return true;
1936   }
1937   gloc = GROUPfind(group, false);
1938   if (GROUPLOCempty(gloc)) {
1939     return false;
1940   }
1941   GROUPlock(gloc, INN_LOCK_WRITE);
1942   ge = &GROUPentries[gloc.recno];
1943   if (ge->count == 0) {
1944     if (lo)
1945       *lo = ge->low;
1946     ge->expired = time(NULL);
1947     GROUPlock(gloc, INN_LOCK_UNLOCK);
1948     return true;
1949   }
1950   flag = ge->flag;
1951   hash = ge->hash;
1952   next = ge->next;
1953   low = ge->low;
1954   high = ge->high;
1955
1956   newge.low = 0;
1957   setinitialge(&newge, hash, &flag, next, 0, high);
1958   if ((handle = ovopensearch(group, low, high, true)) == NULL) {
1959     ge->expired = time(NULL);
1960     GROUPlock(gloc, INN_LOCK_UNLOCK);
1961     syslog(L_ERROR, "%s: could not open overview for '%s'", LocalLogName, group);
1962     return false;
1963   }
1964   while (ovsearch(handle, &artnum, &data, &len, &token, &arrived, &expires)) {
1965     ah = NULL;
1966     if (len == 0)
1967       continue; 
1968     if (!SMprobe(EXPENSIVESTAT, &token, NULL) || OVstatall) {
1969       if ((ah = SMretrieve(token, RETR_STAT)) == NULL)
1970         continue; 
1971       SMfreearticle(ah);
1972     } else {
1973       if (!OVhisthasmsgid(h, data))
1974         continue; 
1975     }
1976     if (innconf->groupbaseexpiry && OVgroupbasedexpire(token, group, data, len, arrived, expires))
1977       continue;
1978 #ifdef OV_DEBUG
1979     if (!ovaddrec(&newge, artnum, token, data, len, arrived, expires, ge)) {
1980 #else
1981     if (!ovaddrec(&newge, artnum, token, data, len, arrived, expires)) {
1982 #endif /* OV_DEBUG */
1983       ovclosesearch(handle, true);
1984       ge->expired = time(NULL);
1985       GROUPlock(gloc, INN_LOCK_UNLOCK);
1986       syslog(L_ERROR, "%s: could not add new overview for '%s'", LocalLogName, group);
1987       return false;
1988     }
1989   }
1990   if (newge.low == 0)
1991     /* no article for the group */
1992     newge.low = newge.high;
1993   *ge = newge;
1994   if (lo) {
1995     if (ge->count == 0)
1996       /* lomark should be himark + 1, if no article for the group */
1997       *lo = ge->low + 1;
1998     else
1999       *lo = ge->low;
2000   }
2001   ovclosesearch(handle, true);
2002   ge->expired = time(NULL);
2003   GROUPlock(gloc, INN_LOCK_UNLOCK);
2004   return true;
2005 }
2006
2007 bool buffindexed_ctl(OVCTLTYPE type, void *val) {
2008   int                   total, used, *i, j;
2009   OVBUFF                *ovbuff = ovbufftab;
2010   OVSORTTYPE            *sorttype;
2011   bool                  *boolval;
2012   GROUPDATABLOCK        *gdb;
2013
2014   switch (type) {
2015   case OVSPACE:
2016     for (total = used = 0 ; ovbuff != (OVBUFF *)NULL ; ovbuff = ovbuff->next) {
2017       ovlock(ovbuff, INN_LOCK_READ);
2018       ovreadhead(ovbuff);
2019       total += ovbuff->totalblk;
2020       used += ovbuff->usedblk;
2021       ovlock(ovbuff, INN_LOCK_UNLOCK);
2022     }
2023     i = (int *)val;
2024     *i = (used * 100) / total;
2025     return true;
2026   case OVSORT:
2027     sorttype = (OVSORTTYPE *)val;
2028     *sorttype = OVNOSORT;
2029     return true;
2030   case OVCUTOFFLOW:
2031     Cutofflow = *(bool *)val;
2032     return true;
2033   case OVSTATICSEARCH:
2034     i = (int *)val;
2035     *i = true;
2036     for (j = 0 ; j < GROUPDATAHASHSIZE ; j++) {
2037       for (gdb = groupdatablock[j] ; gdb != NULL ; gdb = gdb->next) {
2038         if  (gdb->mmapped) {
2039           *i = false;
2040           return true;
2041         }
2042       }
2043     }
2044     return true;
2045   case OVCACHEKEEP:
2046     Cache = *(bool *)val;
2047     return true;
2048   case OVCACHEFREE:
2049     boolval = (bool *)val;
2050     *boolval = true;
2051     if (Gib != NULL) {
2052       free(Gib);
2053       Gib = NULL;
2054       if (Cachesearch != NULL) {
2055         free(Cachesearch->group);
2056         free(Cachesearch);
2057         Cachesearch = NULL;
2058       }
2059     }
2060     return true;
2061   default:
2062     return false;
2063   }
2064 }
2065
2066 void buffindexed_close(void) {
2067   struct stat   sb;
2068   OVBUFF        *ovbuffnext, *ovbuff = ovbufftab;
2069 #ifdef OV_DEBUG
2070   FILE          *F = NULL;
2071   pid_t         pid;
2072   char          *path = NULL;
2073   int           i,j;
2074   struct ov_trace_array *trace;
2075   struct ov_name_table  *ntp;
2076   size_t length;
2077 #endif /* OV_DEBUG */
2078
2079 #ifdef OV_DEBUG
2080   for (; ovbuff != (OVBUFF *)NULL; ovbuff = ovbuff->next) {
2081     for (i = 0 ; i < ovbuff->totalblk ; i++) {
2082       trace = &ovbuff->trace[i];
2083       if (trace->ov_trace == NULL)
2084         continue;
2085       for (j = 0 ; j <= trace->cur && j < trace->max ; j++) {
2086         if (trace->ov_trace[j].occupied != 0 ||
2087           trace->ov_trace[j].freed != 0) {
2088           if (F == NULL) {
2089             length = strlen(innconf->pathtmp) + 11;
2090             path = xmalloc(length);
2091             pid = getpid();
2092             snprintf(path, length, "%s/%d", innconf->pathtmp, pid);
2093             if ((F = fopen(path, "w")) == NULL) {
2094               syslog(L_ERROR, "%s: could not open %s: %m", LocalLogName, path);
2095               break;
2096             }
2097           }
2098           fprintf(F, "%d: % 6d, % 2d: 0x%08x, % 10d, % 10d\n", ovbuff->index, i, j,
2099           trace->ov_trace[j].gloc.recno,
2100           trace->ov_trace[j].occupied,
2101           trace->ov_trace[j].freed);
2102         }
2103       }
2104     }
2105   }
2106   if ((ntp = name_table) != NULL) {
2107     if (F == NULL) {
2108       length = strlen(innconf->pathtmp) + 11;
2109       path = xmalloc(length);
2110       pid = getpid();
2111       sprintf(path, length, "%s/%d", innconf->pathtmp, pid);
2112       if ((F = fopen(path, "w")) == NULL) {
2113         syslog(L_ERROR, "%s: could not open %s: %m", LocalLogName, path);
2114       }
2115     }
2116     if (F != NULL) {
2117       while(ntp) {
2118         fprintf(F, "0x%08x: %s\n", ntp->recno, ntp->name);
2119         ntp = ntp->next;
2120       }
2121     }
2122   }
2123   if (F != NULL)
2124     fclose(F);
2125   if (path != NULL)
2126     free(path);
2127 #endif /* OV_DEBUG */
2128   if (Gib != NULL) {
2129     free(Gib);
2130     Gib = NULL;
2131     if (Cachesearch != NULL) {
2132       free(Cachesearch->group);
2133       free(Cachesearch);
2134       Cachesearch = NULL;
2135     }
2136   }
2137   if (fstat(GROUPfd, &sb) < 0)
2138     return;
2139   close(GROUPfd);
2140
2141   if (GROUPheader) {
2142     if (munmap((void *)GROUPheader, GROUPfilesize(GROUPcount)) < 0) {
2143       syslog(L_FATAL, "%s: could not munmap group.index in buffindexed_close: %m", LocalLogName);
2144       return;
2145     }
2146   }
2147   for (; ovbuff != (OVBUFF *)NULL; ovbuff = ovbuffnext) {
2148     ovbuffnext = ovbuff->next;
2149     free(ovbuff);
2150   }
2151   ovbufftab = NULL;
2152 }
2153
2154 #ifdef BUFF_DEBUG
2155 static int countgdb(void) {
2156   int                   i, count = 0;
2157   GROUPDATABLOCK        *gdb;
2158
2159   for (i = 0 ; i < GROUPDATAHASHSIZE ; i++) {
2160     for (gdb = groupdatablock[i] ; gdb != NULL ; gdb = gdb->next)
2161       count++;
2162   }
2163   return count;
2164 }
2165
2166 main(int argc, char **argv) {
2167   char                  *group, flag[2], buff[OV_BLOCKSIZE];
2168   int                   lo, hi, count, flags, i;
2169   OVSEARCH              *search;
2170   OVBLOCK               *ovblock;
2171   OVINDEX               *ovindex;
2172   OVBUFF                *ovbuff;
2173   GROUPENTRY            *ge;
2174   GROUPLOC              gloc;
2175   GIBLIST               *giblist;
2176
2177   if (argc != 2) {
2178     fprintf(stderr, "only one argument can be specified\n");
2179     exit(1);
2180   }
2181   /* if innconf isn't already read in, do so. */
2182   if (innconf == NULL) {
2183     if (!innconf_read(NULL)) {
2184       fprintf(stderr, "reading inn.conf failed\n");
2185       exit(1);
2186     }
2187   }
2188   if (!buffindexed_open(OV_READ)) {
2189     fprintf(stderr, "buffindexed_open failed\n");
2190     exit(1);
2191   }
2192   fprintf(stdout, "GROUPheader->freelist.recno is %d(0x%08x)\n", GROUPheader->freelist.recno, GROUPheader->freelist.recno);
2193   group = argv[1];
2194   if (isdigit(*group)) {
2195     gloc.recno = atoi(group);
2196     ge = &GROUPentries[gloc.recno];
2197     fprintf(stdout, "left articles are %d for %d, last expiry is %d\n", ge->count, gloc.recno, ge->expired);
2198     if (ge->count == 0) {
2199       GROUPlock(gloc, INN_LOCK_UNLOCK);
2200       exit(0);
2201     }
2202     if (!ovgroupmmap(ge, ge->low, ge->high, true)) {
2203       fprintf(stderr, "ovgroupmmap failed\n");
2204       GROUPlock(gloc, INN_LOCK_UNLOCK);
2205     }
2206     for (giblist = Giblist, i = 0 ; giblist != NULL ; giblist = giblist->next, i++);
2207     fprintf(stdout, "%d index block(s)\n", i);
2208     fprintf(stdout, "%d data block(s)\n", countgdb());
2209     for (giblist = Giblist ; giblist != NULL ; giblist = giblist->next) {
2210       fprintf(stdout, "  % 8d(% 5d)\n", giblist->ov.blocknum, giblist->ov.index);
2211     }
2212     for (i = 0 ; i < Gibcount ; i++) {
2213       if (Gib[i].artnum == 0)
2214         continue;
2215       if (Gib[i].index == NULLINDEX)
2216         fprintf(stdout, "    %d empty\n");
2217       else {
2218         fprintf(stdout, "    %d %d\n", Gib[i].offset, Gib[i].len);
2219       }
2220     }
2221     ovgroupunmap();
2222     GROUPlock(gloc, INN_LOCK_UNLOCK);
2223     exit(0);
2224   }
2225   gloc = GROUPfind(group, false);
2226   if (GROUPLOCempty(gloc)) {
2227     fprintf(stderr, "gloc is null\n");
2228   }
2229   GROUPlock(gloc, INN_LOCK_READ);
2230   ge = &GROUPentries[gloc.recno];
2231   fprintf(stdout, "base %d(%d), cur %d(%d), expired at %s\n", ge->baseindex.blocknum, ge->baseindex.index, ge->curindex.blocknum, ge->curindex.index, ge->expired == 0 ? "none\n" : ctime(&ge->expired));
2232   if (!buffindexed_groupstats(group, &lo, &hi, &count, &flags)) {
2233     fprintf(stderr, "buffindexed_groupstats failed for group %s\n", group);
2234     exit(1);
2235   }
2236   flag[0] = (char)flags;
2237   flag[1] = '\0';
2238   fprintf(stdout, "%s: low is %d, high is %d, count is %d, flag is '%s'\n", group, lo, hi, count, flag);
2239   if ((search = (OVSEARCH *)ovopensearch(group, lo, hi, true)) == NULL) {
2240     fprintf(stderr, "ovopensearch failed for group %s\n", group);
2241     exit(1);
2242   }
2243   fprintf(stdout, "  gloc is %d(0x%08x)\n", search->gloc.recno, search->gloc.recno);
2244   for (giblist = Giblist, i = 0 ; giblist != NULL ; giblist = giblist->next, i++);
2245   fprintf(stdout, "%d index block(s)\n", i);
2246   fprintf(stdout, "%d data block(s)\n", countgdb());
2247   for (giblist = Giblist ; giblist != NULL ; giblist = giblist->next) {
2248     fprintf(stdout, "  % 8d(% 5d)\n", giblist->ov.blocknum, giblist->ov.index);
2249   }
2250   for (i = 0 ; i < Gibcount ; i++) {
2251     if (Gib[i].artnum == 0)
2252       continue;
2253     if (Gib[i].index == NULLINDEX)
2254       fprintf(stdout, "    %d empty\n");
2255     else {
2256       fprintf(stdout, "    %d %d\n", Gib[i].offset, Gib[i].len);
2257     }
2258   }
2259   {
2260     ARTNUM artnum;
2261     char *data;
2262     int len;
2263     TOKEN token;
2264     while (buffindexed_search((void *)search, &artnum, &data, &len, &token, NULL)) {
2265       if (len == 0)
2266         fprintf(stdout, "%d: len is 0\n", artnum);
2267       else {
2268         memcpy(buff, data, len);
2269         buff[len] = '\0';
2270         fprintf(stdout, "%d: %s\n", artnum, buff);
2271       }
2272     }
2273   }
2274 }
2275 #endif /* BUFF_DEBUG */