chiark / gitweb /
debugging for thing that crashed
[innduct.git] / storage / cnfs / cnfs.c
1 /*  $Id: cnfs.c 7412 2005-10-09 03:44:35Z eagle $
2 **
3 **  Cyclic News File System.
4 */
5
6 #include "config.h"
7 #include "clibrary.h"
8 #include "portable/mmap.h"
9 #include "portable/time.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 <netinet/in.h>
17 #include <syslog.h> 
18 #include <sys/stat.h>
19 #include <sys/uio.h>
20
21 #include "inn/innconf.h"
22 #include "interface.h"
23 #include "libinn.h"
24 #include "methods.h"
25 #include "paths.h"
26 #include "inn/wire.h"
27 #include "inn/mmap.h"
28
29 #include "cnfs.h"
30 #include "cnfs-private.h"
31
32 /* Temporary until cnfs_mapcntl is handled like mapcntl.  Make MS_ASYNC
33    disappear on platforms that don't have it. */
34 #ifndef MS_ASYNC
35 # define MS_ASYNC 0
36 #endif
37
38 /* We can give a more descriptive error below about not having largefile           support if the platform has EOVERFLOW; on other platforms some other
39  *    errno will be used and so we won't know when to give the descriptive
40  *       error.  Oh well. */
41 #ifndef EOVERFLOW
42 # define EOVERFLOW 0
43 #endif
44
45 typedef struct {
46     /**** Stuff to be cleaned up when we're done with the article */
47     char                *base;          /* Base of mmap()ed art */
48     int                 len;            /* Length of article (and thus
49                                            mmap()ed art */
50     CYCBUFF             *cycbuff;       /* pointer to current CYCBUFF */
51     off_t               offset;         /* offset to current article */
52     bool                rollover;       /* true if the search is rollovered */
53 } PRIV_CNFS;
54
55 static char LocalLogName[] = "CNFS-sm";
56 static CYCBUFF          *cycbufftab = (CYCBUFF *)NULL;
57 static METACYCBUFF      *metacycbufftab = (METACYCBUFF *)NULL;
58 static CNFSEXPIRERULES  *metaexprulestab = (CNFSEXPIRERULES *)NULL;
59 static long             pagesize = 0;
60 static int              metabuff_update = METACYCBUFF_UPDATE;
61 static int              refresh_interval = REFRESH_INTERVAL;
62
63 static TOKEN CNFSMakeToken(char *cycbuffname, off_t offset,
64                        uint32_t cycnum, STORAGECLASS class) {
65     TOKEN               token;
66     int32_t             int32;
67
68     /*
69     ** XXX We'll assume that TOKENSIZE is 16 bytes and that we divvy it
70     ** up as: 8 bytes for cycbuffname, 4 bytes for offset, 4 bytes
71     ** for cycnum.  See also: CNFSBreakToken() for hard-coded constants.
72     */
73     token.type = TOKEN_CNFS;
74     token.class = class;
75     memcpy(token.token, cycbuffname, CNFSMAXCYCBUFFNAME);
76     int32 = htonl(offset / CNFS_BLOCKSIZE);
77     memcpy(&token.token[8], &int32, sizeof(int32));
78     int32 = htonl(cycnum);
79     memcpy(&token.token[12], &int32, sizeof(int32));
80     return token;
81 }
82
83 /*
84 ** NOTE: We assume that cycbuffname is 9 bytes long.
85 */
86
87 static bool CNFSBreakToken(TOKEN token, char *cycbuffname,
88                            off_t *offset, uint32_t *cycnum) {
89     int32_t     int32;
90
91     if (cycbuffname == NULL || offset == NULL || cycnum == NULL) {
92         syslog(L_ERROR, "%s: BreakToken: invalid argument: %s",
93                LocalLogName, cycbuffname);
94         SMseterror(SMERR_INTERNAL, "BreakToken: invalid argument");
95         return false;
96     }
97     memcpy(cycbuffname, token.token, CNFSMAXCYCBUFFNAME);
98     *(cycbuffname + CNFSMAXCYCBUFFNAME) = '\0'; /* Just to be paranoid */
99     memcpy(&int32, &token.token[8], sizeof(int32));
100     *offset = (off_t)ntohl(int32) * (off_t)CNFS_BLOCKSIZE;
101     memcpy(&int32, &token.token[12], sizeof(int32));
102     *cycnum = ntohl(int32);
103     return true;
104 }
105
106 static char hextbl[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
107                         'a', 'b', 'c', 'd', 'e', 'f'};
108
109 /*
110 ** CNFSofft2hex -- Given an argument of type off_t, return
111 **      a static ASCII string representing its value in hexadecimal.
112 **
113 **      If "leadingzeros" is true, the number returned will have leading 0's.
114 */
115
116 static char * CNFSofft2hex(off_t offset, bool leadingzeros) {
117     static char buf[24];
118     char        *p;
119
120     if (sizeof(off_t) <= 4) {
121         snprintf(buf, sizeof(buf), (leadingzeros) ? "%016lx" : "%lx", offset);
122     } else { 
123         int     i;
124
125         for (i = 0; i < CNFSLASIZ; i++)
126             buf[i] = '0';       /* Pad with zeros to start */
127         for (i = CNFSLASIZ - 1; i >= 0; i--) {
128             buf[i] = hextbl[offset & 0xf];
129             offset >>= 4;
130         }
131     }
132     if (! leadingzeros) {
133         for (p = buf; *p == '0'; p++)
134             ;
135         if (*p != '\0')
136                 return p;
137         else
138                 return p - 1;   /* We converted a "0" and then bypassed all
139                                    the zeros */
140     } else 
141         return buf;
142 }
143
144 /*
145 ** CNFShex2offt -- Given an ASCII string containing a hexadecimal representation
146 **      of a off_t, return a off_t.
147 */
148
149 static off_t CNFShex2offt(char *hex) {
150     if (sizeof(off_t) <= 4) {
151         unsigned long rpofft;
152         /* I'm lazy */
153         sscanf(hex, "%lx", &rpofft);
154         return rpofft;
155     } else {
156         char            diff;
157         off_t           n = 0;
158
159         for (; *hex != '\0'; hex++) {
160             if (*hex >= '0' && *hex <= '9')
161                 diff = '0';
162             else if (*hex >= 'a' && *hex <= 'f')
163                 diff = 'a' - 10;
164             else if (*hex >= 'A' && *hex <= 'F')
165                 diff = 'A' - 10;
166             else {
167                 /*
168                 ** We used to have a syslog() message here, but the case
169                 ** where we land here because of a ":" happens, er, often.
170                 */
171                 break;
172             }
173             n += (*hex - diff);
174             if (isalnum((int)*(hex + 1)))
175                 n <<= 4;
176         }
177         return n;
178     }
179 }
180
181 static bool CNFSflushhead(CYCBUFF *cycbuff) {
182   CYCBUFFEXTERN         rpx;
183
184   if (!cycbuff->needflush)
185     return true;
186   if (!SMopenmode) {
187     syslog(L_ERROR, "%s: CNFSflushhead: attempted flush whilst read only",
188       LocalLogName);
189     return false;
190   }
191   memset(&rpx, 0, sizeof(CYCBUFFEXTERN));
192   if (cycbuff->magicver == 3) {
193     cycbuff->updated = time(NULL);
194     strncpy(rpx.magic, CNFS_MAGICV3, strlen(CNFS_MAGICV3));
195     strncpy(rpx.name, cycbuff->name, CNFSNASIZ);
196     strncpy(rpx.path, cycbuff->path, CNFSPASIZ);
197     /* Don't use sprintf() directly ... the terminating '\0' causes grief */
198     strncpy(rpx.lena, CNFSofft2hex(cycbuff->len, true), CNFSLASIZ);
199     strncpy(rpx.freea, CNFSofft2hex(cycbuff->free, true), CNFSLASIZ);
200     strncpy(rpx.cyclenuma, CNFSofft2hex(cycbuff->cyclenum, true), CNFSLASIZ);
201     strncpy(rpx.updateda, CNFSofft2hex(cycbuff->updated, true), CNFSLASIZ);
202     strncpy(rpx.metaname, cycbuff->metaname, CNFSNASIZ);
203     strncpy(rpx.orderinmeta, CNFSofft2hex(cycbuff->order, true), CNFSLASIZ);
204     if (cycbuff->currentbuff) {
205         strncpy(rpx.currentbuff, "TRUE", CNFSMASIZ);
206     } else {
207         strncpy(rpx.currentbuff, "FALSE", CNFSMASIZ);
208     }
209     memcpy(cycbuff->bitfield, &rpx, sizeof(CYCBUFFEXTERN));
210     msync(cycbuff->bitfield, cycbuff->minartoffset, MS_ASYNC);
211     cycbuff->needflush = false;
212   } else {
213     syslog(L_ERROR, "%s: CNFSflushhead: bogus magicver for %s: %d",
214       LocalLogName, cycbuff->name, cycbuff->magicver);
215     return false;
216   }
217   return true;
218 }
219
220 static void CNFSshutdowncycbuff(CYCBUFF *cycbuff) {
221     if (cycbuff == (CYCBUFF *)NULL)
222         return;
223     if (cycbuff->needflush) {
224         syslog(L_NOTICE, "%s: CNFSshutdowncycbuff: flushing %s", LocalLogName, cycbuff->name);
225         CNFSflushhead(cycbuff);
226     }
227     if (cycbuff->bitfield != NULL) {
228         munmap(cycbuff->bitfield, cycbuff->minartoffset);
229         cycbuff->bitfield = NULL;
230     }
231     if (cycbuff->fd >= 0)
232         close(cycbuff->fd);
233     cycbuff->fd = -1;
234 }
235
236 static void CNFScleancycbuff(void) {
237     CYCBUFF     *cycbuff, *nextcycbuff;
238
239     for (cycbuff = cycbufftab; cycbuff != (CYCBUFF *)NULL;) {
240       CNFSshutdowncycbuff(cycbuff);
241       nextcycbuff = cycbuff->next;
242       free(cycbuff);
243       cycbuff = nextcycbuff;
244     }
245     cycbufftab = (CYCBUFF *)NULL;
246 }
247
248 static void CNFScleanmetacycbuff(void) {
249     METACYCBUFF *metacycbuff, *nextmetacycbuff;
250
251     for (metacycbuff = metacycbufftab; metacycbuff != (METACYCBUFF *)NULL;) {
252       nextmetacycbuff = metacycbuff->next;
253       free(metacycbuff->members);
254       free(metacycbuff->name);
255       free(metacycbuff);
256       metacycbuff = nextmetacycbuff;
257     }
258     metacycbufftab = (METACYCBUFF *)NULL;
259 }
260
261 static void CNFScleanexpirerule(void) {
262     CNFSEXPIRERULES     *metaexprule, *nextmetaexprule;
263
264     for (metaexprule = metaexprulestab; metaexprule != (CNFSEXPIRERULES *)NULL;) {
265       nextmetaexprule = metaexprule->next;
266       free(metaexprule);
267       metaexprule = nextmetaexprule;
268     }
269     metaexprulestab = (CNFSEXPIRERULES *)NULL;
270 }
271
272 static CYCBUFF *CNFSgetcycbuffbyname(char *name) {
273     CYCBUFF     *cycbuff;
274  
275     if (name == NULL)
276         return NULL;
277     for (cycbuff = cycbufftab; cycbuff != (CYCBUFF *)NULL; cycbuff = cycbuff->next)
278         if (strcmp(name, cycbuff->name) == 0) 
279             return cycbuff;
280     return NULL;
281 }
282
283 static METACYCBUFF *CNFSgetmetacycbuffbyname(char *name) {
284   METACYCBUFF   *metacycbuff;
285
286   if (name == NULL)
287     return NULL;
288   for (metacycbuff = metacycbufftab; metacycbuff != (METACYCBUFF *)NULL; metacycbuff = metacycbuff->next)
289     if (strcmp(name, metacycbuff->name) == 0) 
290       return metacycbuff;
291   return NULL;
292 }
293
294 static void CNFSflushallheads(void) {
295   CYCBUFF       *cycbuff;
296
297   for (cycbuff = cycbufftab; cycbuff != (CYCBUFF *)NULL; cycbuff = cycbuff->next) {
298     if (cycbuff->needflush)
299         syslog(L_NOTICE, "%s: CNFSflushallheads: flushing %s", LocalLogName, cycbuff->name);
300     CNFSflushhead(cycbuff);
301   }
302 }
303
304 /*
305 ** CNFSReadFreeAndCycle() -- Read from disk the current values of CYCBUFF's
306 **      free pointer and cycle number.  Return 1 on success, 0 otherwise.
307 */
308
309 static void CNFSReadFreeAndCycle(CYCBUFF *cycbuff) {
310     CYCBUFFEXTERN       rpx;
311     char                buf[64];
312
313     memcpy(&rpx, cycbuff->bitfield, sizeof(CYCBUFFEXTERN));
314     /* Sanity checks are not needed since CNFSinit_disks() has already done. */
315     strncpy(buf, rpx.freea, CNFSLASIZ);
316     buf[CNFSLASIZ] = '\0';
317     cycbuff->free = CNFShex2offt(buf);
318     strncpy(buf, rpx.updateda, CNFSLASIZ);
319     buf[CNFSLASIZ] = '\0';
320     cycbuff->updated = CNFShex2offt(buf);
321     strncpy(buf, rpx.cyclenuma, CNFSLASIZ);
322     buf[CNFSLASIZ] = '\0';
323     cycbuff->cyclenum = CNFShex2offt(buf);
324     return;
325 }
326
327 static bool CNFSparse_part_line(char *l) {
328   char          *p;
329   struct stat   sb;
330   off_t         len, minartoffset;
331   int           tonextblock;
332   CYCBUFF       *cycbuff, *tmp;
333
334   /* Symbolic cnfs partition name */
335   if ((p = strchr(l, ':')) == NULL || p - l <= 0 || p - l > CNFSMAXCYCBUFFNAME - 1) {
336     syslog(L_ERROR, "%s: bad cycbuff name in line '%s'", LocalLogName, l);
337     return false;
338   }
339   *p = '\0';
340   if (CNFSgetcycbuffbyname(l) != NULL) {
341     *p = ':';
342     syslog(L_ERROR, "%s: duplicate cycbuff name in line '%s'", LocalLogName, l);
343     return false;
344   }
345   cycbuff = xmalloc(sizeof(CYCBUFF));
346   memset(cycbuff->name, '\0', CNFSNASIZ);
347   strlcpy(cycbuff->name, l, CNFSNASIZ);
348   l = ++p;
349
350   /* Path to cnfs partition */
351   if ((p = strchr(l, ':')) == NULL || p - l <= 0 || p - l > CNFSPASIZ - 1) {
352     syslog(L_ERROR, "%s: bad pathname in line '%s'", LocalLogName, l);
353     free(cycbuff);
354     return false;
355   }
356   *p = '\0';
357   memset(cycbuff->path, '\0', CNFSPASIZ);
358   strlcpy(cycbuff->path, l, CNFSPASIZ);
359   if (stat(cycbuff->path, &sb) < 0) {
360     if (errno == EOVERFLOW) {
361       syslog(L_ERROR, "%s: file '%s' : %s, ignoring '%s' cycbuff",
362              LocalLogName, cycbuff->path,
363              "Overflow (probably >2GB without largefile support)",
364              cycbuff->name);
365     } else {
366       syslog(L_ERROR, "%s: file '%s' : %m, ignoring '%s' cycbuff",
367              LocalLogName, cycbuff->path, cycbuff->name);
368     }
369     free(cycbuff);
370     return false;
371   }
372   l = ++p;
373
374   /* Length/size of symbolic partition */
375   len = strtoul(l, NULL, 10) * (off_t)1024;     /* This value in KB in decimal */
376   if (S_ISREG(sb.st_mode) && len != sb.st_size) {
377     if (sizeof(CYCBUFFEXTERN) > (size_t) sb.st_size) {
378       syslog(L_NOTICE, "%s: length must be at least '%lu' for '%s' cycbuff(%lu bytes)",
379         LocalLogName, (unsigned long) sizeof(CYCBUFFEXTERN), cycbuff->name,
380         (unsigned long) sb.st_size);
381       free(cycbuff);
382       return false;
383     }
384   }
385   cycbuff->len = len;
386   cycbuff->fd = -1;
387   cycbuff->next = (CYCBUFF *)NULL;
388   cycbuff->needflush = false;
389   cycbuff->bitfield = NULL;
390   /*
391   ** The minimum article offset will be the size of the bitfield itself,
392   ** len / (blocksize * 8), plus however many additional blocks the CYCBUFF
393   ** external header occupies ... then round up to the next block.
394   */
395   minartoffset =
396       cycbuff->len / (CNFS_BLOCKSIZE * 8) + CNFS_BEFOREBITF;
397   tonextblock = CNFS_HDR_PAGESIZE - (minartoffset & (CNFS_HDR_PAGESIZE - 1));
398   cycbuff->minartoffset = minartoffset + tonextblock;
399
400   if (cycbufftab == (CYCBUFF *)NULL)
401     cycbufftab = cycbuff;
402   else {
403     for (tmp = cycbufftab; tmp->next != (CYCBUFF *)NULL; tmp = tmp->next);
404     tmp->next = cycbuff;
405   }
406   /* Done! */
407   return true;
408 }
409
410 static bool CNFSparse_metapart_line(char *l) {
411   char          *p, *cycbuff, *q = l;
412   CYCBUFF       *rp;
413   METACYCBUFF   *metacycbuff, *tmp;
414
415   /* Symbolic metacycbuff name */
416   if ((p = strchr(l, ':')) == NULL || p - l <= 0) {
417     syslog(L_ERROR, "%s: bad partition name in line '%s'", LocalLogName, l);
418     return false;
419   }
420   *p = '\0';
421   if (CNFSgetmetacycbuffbyname(l) != NULL) {
422     *p = ':';
423     syslog(L_ERROR, "%s: duplicate metabuff name in line '%s'", LocalLogName, l);
424     return false;
425   }
426   metacycbuff = xmalloc(sizeof(METACYCBUFF));
427   metacycbuff->members = (CYCBUFF **)NULL;
428   metacycbuff->count = 0;
429   metacycbuff->name = xstrdup(l);
430   metacycbuff->next = (METACYCBUFF *)NULL;
431   metacycbuff->metamode = INTERLEAVE;
432   l = ++p;
433
434   if ((p = strchr(l, ':')) != NULL) {
435       if (p - l <= 0) {
436         syslog(L_ERROR, "%s: bad mode in line '%s'", LocalLogName, q);
437         return false;
438       }
439       if (strcmp(++p, "INTERLEAVE") == 0)
440         metacycbuff->metamode = INTERLEAVE;
441       else if (strcmp(p, "SEQUENTIAL") == 0)
442         metacycbuff->metamode = SEQUENTIAL;
443       else {
444         syslog(L_ERROR, "%s: unknown mode in line '%s'", LocalLogName, q);
445         return false;
446       }
447       *--p = '\0';
448   }
449   /* Cycbuff list */
450   while ((p = strchr(l, ',')) != NULL && p - l > 0) {
451     *p = '\0';
452     cycbuff = l;
453     l = ++p;
454     if ((rp = CNFSgetcycbuffbyname(cycbuff)) == NULL) {
455       syslog(L_ERROR, "%s: bogus cycbuff '%s' (metacycbuff '%s')",
456              LocalLogName, cycbuff, metacycbuff->name);
457       free(metacycbuff->members);
458       free(metacycbuff->name);
459       free(metacycbuff);
460       return false;
461     }
462     if (metacycbuff->count == 0)
463       metacycbuff->members = xmalloc(sizeof(CYCBUFF *));
464     else
465       metacycbuff->members = xrealloc(metacycbuff->members, (metacycbuff->count + 1) * sizeof(CYCBUFF *));
466     metacycbuff->members[metacycbuff->count++] = rp;
467   }
468   /* Gotta deal with the last cycbuff on the list */
469   cycbuff = l;
470   if ((rp = CNFSgetcycbuffbyname(cycbuff)) == NULL) {
471     syslog(L_ERROR, "%s: bogus cycbuff '%s' (metacycbuff '%s')",
472            LocalLogName, cycbuff, metacycbuff->name);
473     free(metacycbuff->members);
474     free(metacycbuff->name);
475     free(metacycbuff);
476     return false;
477   } else {
478     if (metacycbuff->count == 0)
479       metacycbuff->members = xmalloc(sizeof(CYCBUFF *));
480     else
481       metacycbuff->members = xrealloc(metacycbuff->members, (metacycbuff->count + 1) * sizeof(CYCBUFF *));
482     metacycbuff->members[metacycbuff->count++] = rp;
483   }
484   
485   if (metacycbuff->count == 0) {
486     syslog(L_ERROR, "%s: no cycbuffs assigned to cycbuff '%s'",
487            LocalLogName, metacycbuff->name);
488     free(metacycbuff->name);
489     free(metacycbuff);
490     return false;
491   }
492   if (metacycbufftab == (METACYCBUFF *)NULL)
493     metacycbufftab = metacycbuff;
494   else {
495     for (tmp = metacycbufftab; tmp->next != (METACYCBUFF *)NULL; tmp = tmp->next);
496     tmp->next = metacycbuff;
497   }
498   /* DONE! */
499   return true;
500 }
501
502 static bool CNFSparse_groups_line(void) {
503   METACYCBUFF   *mrp;
504   STORAGE_SUB   *sub = (STORAGE_SUB *)NULL;
505   CNFSEXPIRERULES       *metaexprule, *tmp;
506
507   sub = SMGetConfig(TOKEN_CNFS, sub);
508   for (;sub != (STORAGE_SUB *)NULL; sub = SMGetConfig(TOKEN_CNFS, sub)) {
509     if (sub->options == (char *)NULL) {
510       syslog(L_ERROR, "%s: storage.conf options field is missing",
511            LocalLogName);
512       CNFScleanexpirerule();
513       return false;
514     }
515     if ((mrp = CNFSgetmetacycbuffbyname(sub->options)) == NULL) {
516       syslog(L_ERROR, "%s: storage.conf options field '%s' undefined",
517            LocalLogName, sub->options);
518       CNFScleanexpirerule();
519       return false;
520     }
521     metaexprule = xmalloc(sizeof(CNFSEXPIRERULES));
522     metaexprule->class = sub->class;
523     metaexprule->dest = mrp;
524     metaexprule->next = (CNFSEXPIRERULES *)NULL;
525     if (metaexprulestab == (CNFSEXPIRERULES *)NULL)
526       metaexprulestab = metaexprule;
527     else {
528       for (tmp = metaexprulestab; tmp->next != (CNFSEXPIRERULES *)NULL; tmp = tmp->next);
529       tmp->next = metaexprule;
530     }
531   }
532   /* DONE! */
533   return true;
534 }
535
536 /*
537 ** CNFSinit_disks -- Finish initializing cycbufftab
538 **      Called by "innd" only -- we open (and keep) a read/write
539 **      file descriptor for each CYCBUFF.
540 **
541 ** Calling this function repeatedly shouldn't cause any harm
542 ** speed-wise or bug-wise, as long as the caller is accessing the
543 ** CYCBUFFs _read-only_.  If innd calls this function repeatedly,
544 ** bad things will happen.
545 */
546
547 static bool CNFSinit_disks(CYCBUFF *cycbuff) {
548   char          buf[64];
549   CYCBUFFEXTERN *rpx;
550   int           fd;
551   off_t         tmpo;
552   bool          oneshot;
553
554   /*
555   ** Discover the state of our cycbuffs.  If any of them are in icky shape,
556   ** duck shamelessly & return false.
557   */
558
559   if (cycbuff != (CYCBUFF *)NULL)
560     oneshot = true;
561   else {
562     oneshot = false;
563     cycbuff = cycbufftab;
564   }
565   for (; cycbuff != (CYCBUFF *)NULL; cycbuff = cycbuff->next) {
566     if (strcmp(cycbuff->path, "/dev/null") == 0) {
567         syslog(L_ERROR, "%s: ERROR opening '%s' is not available",
568                 LocalLogName, cycbuff->path);
569         return false;
570     }
571     if (cycbuff->fd < 0) {
572         if ((fd = open(cycbuff->path, SMopenmode ? O_RDWR : O_RDONLY)) < 0) {
573             syslog(L_ERROR, "%s: ERROR opening '%s' O_RDONLY : %m",
574                    LocalLogName, cycbuff->path);
575             return false;
576         } else {
577             close_on_exec(fd, true);
578             cycbuff->fd = fd;
579         }
580     }
581     errno = 0;
582     cycbuff->bitfield = mmap(NULL, cycbuff->minartoffset,
583                              SMopenmode ? (PROT_READ | PROT_WRITE) : PROT_READ,
584                              MAP_SHARED, cycbuff->fd, 0);
585     if (cycbuff->bitfield == MAP_FAILED || errno != 0) {
586         syslog(L_ERROR,
587                "%s: CNFSinitdisks: mmap for %s offset %d len %ld failed: %m",
588                LocalLogName, cycbuff->path, 0, (long) cycbuff->minartoffset);
589         cycbuff->bitfield = NULL;
590         return false;
591     }
592
593     /*
594     ** Much of this checking from previous revisions is (probably) bogus
595     ** & buggy & particularly icky & unupdated.  Use at your own risk.  :-)
596     */
597     rpx = (CYCBUFFEXTERN *)cycbuff->bitfield;
598     if (strncmp(rpx->magic, CNFS_MAGICV3, strlen(CNFS_MAGICV3)) == 0) {
599         cycbuff->magicver = 3;
600         if (strncmp(rpx->name, cycbuff->name, CNFSNASIZ) != 0) {
601             syslog(L_ERROR, "%s: Mismatch 3: read %s for cycbuff %s", LocalLogName,
602                    rpx->name, cycbuff->name);
603             return false;
604         }
605         if (strncmp(rpx->path, cycbuff->path, CNFSPASIZ) != 0) {
606             syslog(L_ERROR, "%s: Path mismatch: read %s for cycbuff %s",
607                    LocalLogName, rpx->path, cycbuff->path);
608         } 
609         strncpy(buf, rpx->lena, CNFSLASIZ);
610         buf[CNFSLASIZ] = '\0';
611         tmpo = CNFShex2offt(buf);
612         if (tmpo != cycbuff->len) {
613             syslog(L_ERROR, "%s: Mismatch: read 0x%s length for cycbuff %s",
614                    LocalLogName, CNFSofft2hex(tmpo, false), cycbuff->path);
615             return false;
616         }
617         strncpy(buf, rpx->freea, CNFSLASIZ);
618         buf[CNFSLASIZ] = '\0';
619         cycbuff->free = CNFShex2offt(buf);
620         strncpy(buf, rpx->updateda, CNFSLASIZ);
621         buf[CNFSLASIZ] = '\0';
622         cycbuff->updated = CNFShex2offt(buf);
623         strncpy(buf, rpx->cyclenuma, CNFSLASIZ);
624         buf[CNFSLASIZ] = '\0';
625         cycbuff->cyclenum = CNFShex2offt(buf);
626         strncpy(cycbuff->metaname, rpx->metaname, CNFSLASIZ);
627         strncpy(buf, rpx->orderinmeta, CNFSLASIZ);
628         cycbuff->order = CNFShex2offt(buf);
629         if (strncmp(rpx->currentbuff, "TRUE", CNFSMASIZ) == 0) {
630             cycbuff->currentbuff = true;
631         } else
632             cycbuff->currentbuff = false;
633     } else {
634         syslog(L_NOTICE,
635                 "%s: No magic cookie found for cycbuff %s, initializing",
636                 LocalLogName, cycbuff->name);
637         cycbuff->magicver = 3;
638         cycbuff->free = cycbuff->minartoffset;
639         cycbuff->updated = 0;
640         cycbuff->cyclenum = 1;
641         cycbuff->currentbuff = true;
642         cycbuff->order = 0;     /* to indicate this is newly added cycbuff */
643         cycbuff->needflush = true;
644         memset(cycbuff->metaname, '\0', CNFSLASIZ);
645         if (!CNFSflushhead(cycbuff))
646             return false;
647     }
648     if (oneshot)
649       break;
650   }
651   return true;
652 }
653
654 static bool CNFS_setcurrent(METACYCBUFF *metacycbuff) {
655   CYCBUFF       *cycbuff;
656   int           i, currentcycbuff = 0, order = -1;
657   bool          foundcurrent = false;
658   for (i = 0 ; i < metacycbuff->count ; i++) {
659     cycbuff = metacycbuff->members[i];
660     if (strncmp(cycbuff->metaname, metacycbuff->name, CNFSNASIZ) != 0) {
661       /* this cycbuff is moved from other metacycbuff , or is new */
662       cycbuff->order = i + 1;
663       cycbuff->currentbuff = false;
664       strncpy(cycbuff->metaname, metacycbuff->name, CNFSLASIZ);
665       cycbuff->needflush = true;
666       continue;
667     }
668     if (foundcurrent == false && cycbuff->currentbuff == true) {
669       currentcycbuff = i;
670       foundcurrent = true;
671     }
672     if (foundcurrent == false || order == -1 || order > cycbuff->order) {
673       /* this cycbuff is a candidate for current cycbuff */
674       currentcycbuff = i;
675       order = cycbuff->order;
676     }
677     if (cycbuff->order != i + 1) {
678       /* cycbuff order seems to be changed */
679       cycbuff->order = i + 1;
680       cycbuff->needflush = true;
681     }
682   }
683   /* If no current cycbuff found (say, all our cycbuffs are new) default to 0 */
684   if (foundcurrent == false) {
685     currentcycbuff = 0;
686   }
687   for (i = 0 ; i < metacycbuff->count ; i++) {
688     cycbuff = metacycbuff->members[i];
689     if (currentcycbuff == i && cycbuff->currentbuff == false) {
690       cycbuff->currentbuff = true;
691       cycbuff->needflush = true;
692     }
693     if (currentcycbuff != i && cycbuff->currentbuff == true) {
694       cycbuff->currentbuff = false;
695       cycbuff->needflush = true;
696     }
697     if (cycbuff->needflush == true && !CNFSflushhead(cycbuff))
698         return false;
699   }
700   metacycbuff->memb_next = currentcycbuff;
701   return true;
702 }
703
704 /*
705 ** CNFSread_config() -- Read the cnfs partition/file configuration file.
706 **
707 ** Oh, for the want of Perl!  My parser probably shows that I don't use
708 ** C all that often anymore....
709 */
710
711 static bool CNFSread_config(void) {
712     char        *path, *config, *from, *to, **ctab = (char **)NULL;
713     int         ctab_free = 0;  /* Index to next free slot in ctab */
714     int         ctab_i;
715     bool        metacycbufffound = false;
716     bool        cycbuffupdatefound = false;
717     bool        refreshintervalfound = false;
718     int         update, refresh;
719
720     path = concatpath(innconf->pathetc, _PATH_CYCBUFFCONFIG);
721     config = ReadInFile(path, NULL);
722     if (config == NULL) {
723         syslog(L_ERROR, "%s: cannot read %s", LocalLogName, path);
724         free(config);
725         free(path);
726         return false;
727     }
728     free(path);
729     for (from = to = config; *from; ) {
730         if (*from == '#') {     /* Comment line? */
731             while (*from && *from != '\n')
732                 from++;                         /* Skip past it */
733             from++;
734             continue;                           /* Back to top of loop */
735         }
736         if (*from == '\n') {    /* End or just a blank line? */
737             from++;
738             continue;                           /* Back to top of loop */
739         }
740         if (ctab_free == 0)
741           ctab = xmalloc(sizeof(char *));
742         else
743           ctab = xrealloc(ctab, (ctab_free + 1) * sizeof(char *));
744         /* If we're here, we've got the beginning of a real entry */
745         ctab[ctab_free++] = to = from;
746         while (1) {
747             if (*from && *from == '\\' && *(from + 1) == '\n') {
748                 from += 2;              /* Skip past backslash+newline */
749                 while (*from && isspace((int)*from))
750                     from++;
751                 continue;
752             }
753             if (*from && *from != '\n')
754                 *to++ = *from++;
755             if (*from == '\n') {
756                 *to++ = '\0';
757                 from++;
758                 break;
759             }
760             if (! *from)
761                 break;
762         }
763     }
764
765     for (ctab_i = 0; ctab_i < ctab_free; ctab_i++) {
766         if (strncmp(ctab[ctab_i], "cycbuff:", 8) == 0) {
767             if (metacycbufffound) {
768                 syslog(L_ERROR, "%s: all cycbuff entries shoud be before metacycbuff entries", LocalLogName);
769                 free(config);
770                 free(ctab);
771                 return false;
772             }
773             if (!CNFSparse_part_line(ctab[ctab_i] + 8)) {
774                 free(config);
775                 free(ctab);
776                 return false;
777             }
778         } else if (strncmp(ctab[ctab_i], "metacycbuff:", 12) == 0) {
779             metacycbufffound = true;
780             if (!CNFSparse_metapart_line(ctab[ctab_i] + 12)) {
781                 free(config);
782                 free(ctab);
783                 return false;
784             }
785         } else if (strncmp(ctab[ctab_i], "cycbuffupdate:", 14) == 0) {
786             if (cycbuffupdatefound) {
787                 syslog(L_ERROR, "%s: duplicate cycbuffupdate entries", LocalLogName);
788                 free(config);
789                 free(ctab);
790                 return false;
791             }
792             cycbuffupdatefound = true;
793             update = atoi(ctab[ctab_i] + 14);
794             if (update < 0) {
795                 syslog(L_ERROR, "%s: invalid cycbuffupdate", LocalLogName);
796                 free(config);
797                 free(ctab);
798                 return false;
799             }
800             if (update == 0)
801                 metabuff_update = METACYCBUFF_UPDATE;
802             else
803                 metabuff_update = update;
804         } else if (strncmp(ctab[ctab_i], "refreshinterval:", 16) == 0) {
805             if (refreshintervalfound) {
806                 syslog(L_ERROR, "%s: duplicate refreshinterval entries", LocalLogName);
807                 free(config);
808                 free(ctab);
809                 return false;
810             }
811             refreshintervalfound = true;
812             refresh = atoi(ctab[ctab_i] + 16);
813             if (refresh < 0) {
814                 syslog(L_ERROR, "%s: invalid refreshinterval", LocalLogName);
815                 free(config);
816                 free(ctab);
817                 return false;
818             }
819             if (refresh == 0)
820                 refresh_interval = REFRESH_INTERVAL;
821             else
822                 refresh_interval = refresh;
823         } else {
824             syslog(L_ERROR, "%s: Bogus metacycbuff config line '%s' ignored",
825                    LocalLogName, ctab[ctab_i]);
826         }
827     }
828     free(config);
829     free(ctab);
830     if (!CNFSparse_groups_line()) {
831         return false;
832     }
833     if (cycbufftab == (CYCBUFF *)NULL) {
834         syslog(L_ERROR, "%s: zero cycbuffs defined", LocalLogName);
835         return false;
836     }
837     if (metacycbufftab == (METACYCBUFF *)NULL) {
838         syslog(L_ERROR, "%s: zero metacycbuffs defined", LocalLogName);
839         return false;
840     }
841     return true;
842 }
843
844 /* Figure out what page an address is in and flush those pages */
845 static void
846 cnfs_mapcntl(void *p, size_t length, int flags)
847 {
848     char *start, *end;
849
850     start = (char *)((size_t)p & ~(size_t)(pagesize - 1));
851     end = (char *)((size_t)((char *)p + length + pagesize) &
852                    ~(size_t)(pagesize - 1));
853     if (flags == MS_INVALIDATE) {
854         msync(start, end - start, flags);
855     } else {
856         static char *sstart, *send;
857
858         /* Don't thrash the system with msync()s - keep the last value
859          * and check each time, only if the pages which we should
860          * flush change actually flush the previous ones. Calling
861          * cnfs_mapcntl(NULL, 0, MS_ASYNC) then flushes the final
862          * piece */
863         if (start != sstart || end != send) {
864             if (sstart != NULL && send != NULL) {
865                 msync(sstart, send - sstart, flags);
866             }
867             sstart = start;
868             send = send;
869         }
870     }
871 }
872
873 /*
874 **      Bit arithmetic by brute force.
875 **
876 **      XXXYYYXXX WARNING: the code below is not endian-neutral!
877 */
878
879 typedef unsigned long   ULONG;
880
881 static int CNFSUsedBlock(CYCBUFF *cycbuff, off_t offset,
882               bool set_operation, bool setbitvalue) {
883     off_t               blocknum;
884     off_t               longoffset;
885     int                 bitoffset;      /* From the 'left' side of the long */
886     static int          uninitialized = 1;
887     static int          longsize = sizeof(long);
888     int i;
889     ULONG               bitlong, on, off, mask;
890     static ULONG        onarray[64], offarray[64];
891     ULONG               *where;
892
893     if (uninitialized) {
894         on = 1;
895         off = on;
896         off ^= ULONG_MAX;
897         for (i = (longsize * 8) - 1; i >= 0; i--) {
898             onarray[i] = on;
899             offarray[i] = off;
900             on <<= 1;
901             off = on;
902             off ^= ULONG_MAX;
903         }
904         uninitialized = 0;
905     }
906
907     /* We allow bit-setting under minartoffset, but it better be false */
908     if ((offset < cycbuff->minartoffset && setbitvalue) ||
909         offset > cycbuff->len) {
910         char    bufoff[64], bufmin[64], bufmax[64];
911         SMseterror(SMERR_INTERNAL, NULL);
912         strlcpy(bufoff, CNFSofft2hex(offset, false), sizeof(bufoff));
913         strlcpy(bufmin, CNFSofft2hex(cycbuff->minartoffset, false),
914                 sizeof(bufmin));
915         strlcpy(bufmax, CNFSofft2hex(cycbuff->len, false), sizeof(bufmax));
916         syslog(L_ERROR,
917                "%s: CNFSUsedBlock: invalid offset %s, min = %s, max = %s",
918                LocalLogName, bufoff, bufmin, bufmax);
919         return 0;
920     }
921     if (offset % CNFS_BLOCKSIZE != 0) {
922         SMseterror(SMERR_INTERNAL, NULL);
923         syslog(L_ERROR,
924                "%s: CNFSsetusedbitbyrp: offset %s not on %d-byte block boundary",
925                LocalLogName, CNFSofft2hex(offset, false), CNFS_BLOCKSIZE);
926         return 0;
927     }
928     blocknum = offset / CNFS_BLOCKSIZE;
929     longoffset = blocknum / (longsize * 8);
930     bitoffset = blocknum % (longsize * 8);
931     where = (ULONG *)cycbuff->bitfield + (CNFS_BEFOREBITF / longsize)
932         + longoffset;
933     bitlong = *where;
934     if (set_operation) {
935         if (setbitvalue) {
936             mask = onarray[bitoffset];
937             bitlong |= mask;
938         } else {
939             mask = offarray[bitoffset];
940             bitlong &= mask;
941         }
942         *where = bitlong;
943         if (innconf->nfswriter) {
944             cnfs_mapcntl(where, sizeof *where, MS_ASYNC);
945         }
946         return 2;       /* XXX Clean up return semantics */
947     }
948     /* It's a read operation */
949     mask = onarray[bitoffset];
950
951 /* 
952  * return bitlong & mask; doesn't work if sizeof(ulong) > sizeof(int)
953  */
954     if ( bitlong & mask ) return 1; else return 0;
955
956 }
957
958 static int CNFSArtMayBeHere(CYCBUFF *cycbuff, off_t offset, uint32_t cycnum) {
959     static time_t       lastupdate = 0;
960     CYCBUFF             *tmp;
961
962     if (SMpreopen && !SMopenmode) {
963         if ((time(NULL) - lastupdate) > refresh_interval) {     /* XXX Changed to refresh every 30sec - cmo*/
964             for (tmp = cycbufftab; tmp != (CYCBUFF *)NULL; tmp = tmp->next) {
965                 CNFSReadFreeAndCycle(tmp);
966             }
967             lastupdate = time(NULL);
968         } else if (cycnum == cycbuff->cyclenum + 1) {   /* rollover ? */
969             CNFSReadFreeAndCycle(cycbuff);
970         }
971     }
972     /*
973     ** The current cycle number may have advanced since the last time we
974     ** checked it, so use a ">=" check instead of "==".  Our intent is
975     ** avoid a false negative response, *not* a false positive response.
976     */
977     if (! (cycnum == cycbuff->cyclenum ||
978         (cycnum == cycbuff->cyclenum - 1 && offset > cycbuff->free) ||
979         (cycnum + 1 == 0 && cycbuff->cyclenum == 2 && offset > cycbuff->free))) {
980         /* We've been overwritten */
981         return 0;
982     }
983     return CNFSUsedBlock(cycbuff, offset, false, false);
984 }
985
986 bool cnfs_init(SMATTRIBUTE *attr) {
987     METACYCBUFF *metacycbuff;
988     CYCBUFF     *cycbuff;
989
990     if (attr == NULL) {
991         syslog(L_ERROR, "%s: attr is NULL", LocalLogName);
992         SMseterror(SMERR_INTERNAL, "attr is NULL");
993         return false;
994     }
995     attr->selfexpire = true;
996     attr->expensivestat = false;
997     if (innconf == NULL) {
998         if (!innconf_read(NULL)) {
999             syslog(L_ERROR, "%s: innconf_read failed", LocalLogName);
1000             SMseterror(SMERR_INTERNAL, "ReadInnConf() failed");
1001             return false;
1002         }
1003     }
1004     if (pagesize == 0) {
1005         pagesize = getpagesize();
1006         if (pagesize == -1) {
1007             syslog(L_ERROR, "%s: getpagesize failed: %m", LocalLogName);
1008             SMseterror(SMERR_INTERNAL, "getpagesize failed");
1009             pagesize = 0;
1010             return false;
1011         }
1012         if ((pagesize > CNFS_HDR_PAGESIZE) || (CNFS_HDR_PAGESIZE % pagesize)) {
1013             syslog(L_ERROR, "%s: CNFS_HDR_PAGESIZE (%d) is not a multiple of pagesize (%ld)", LocalLogName, CNFS_HDR_PAGESIZE, pagesize);
1014             SMseterror(SMERR_INTERNAL, "CNFS_HDR_PAGESIZE not multiple of pagesize");
1015             return false;
1016         }
1017     }
1018     if (STORAGE_TOKEN_LENGTH < 16) {
1019         syslog(L_ERROR, "%s: token length is less than 16 bytes", LocalLogName);
1020         SMseterror(SMERR_TOKENSHORT, NULL);
1021         return false;
1022     }
1023
1024     if (!CNFSread_config()) {
1025         CNFScleancycbuff();
1026         CNFScleanmetacycbuff();
1027         CNFScleanexpirerule();
1028         SMseterror(SMERR_INTERNAL, NULL);
1029         return false;
1030     }
1031     if (!CNFSinit_disks(NULL)) {
1032         CNFScleancycbuff();
1033         CNFScleanmetacycbuff();
1034         CNFScleanexpirerule();
1035         SMseterror(SMERR_INTERNAL, NULL);
1036         return false;
1037     }
1038     for (metacycbuff = metacycbufftab; metacycbuff != (METACYCBUFF *)NULL; metacycbuff = metacycbuff->next) {
1039       metacycbuff->memb_next = 0;
1040       metacycbuff->write_count = 0;             /* Let's not forget this */
1041       if (metacycbuff->metamode == SEQUENTIAL)
1042         /* mark current cycbuff */
1043         if (CNFS_setcurrent(metacycbuff) == false) {
1044           CNFScleancycbuff();
1045           CNFScleanmetacycbuff();
1046           CNFScleanexpirerule();
1047           SMseterror(SMERR_INTERNAL, NULL);
1048           return false;
1049         }
1050     }
1051     if (!SMpreopen) {
1052       for (cycbuff = cycbufftab; cycbuff != (CYCBUFF *)NULL; cycbuff = cycbuff->next) {
1053         CNFSshutdowncycbuff(cycbuff);
1054       }
1055     }
1056     return true;
1057 }
1058
1059 TOKEN cnfs_store(const ARTHANDLE article, const STORAGECLASS class) {
1060     TOKEN               token;
1061     CYCBUFF             *cycbuff = NULL;
1062     METACYCBUFF         *metacycbuff = NULL;
1063     int                 i;
1064     static char         buf[1024];
1065     static char         alignbuf[CNFS_BLOCKSIZE];
1066     char                *artcycbuffname;
1067     off_t               artoffset, middle;
1068     uint32_t            artcyclenum;
1069     CNFSARTHEADER       cah;
1070     static struct iovec *iov;
1071     static int          iovcnt;
1072     int                 tonextblock;
1073     CNFSEXPIRERULES     *metaexprule;
1074     off_t               left;
1075     size_t              totlen;
1076
1077     for (metaexprule = metaexprulestab; metaexprule != (CNFSEXPIRERULES *)NULL; metaexprule = metaexprule->next) {
1078         if (metaexprule->class == class)
1079             break;
1080     }
1081     if (metaexprule == (CNFSEXPIRERULES *)NULL) {
1082         SMseterror(SMERR_INTERNAL, "no rules match");
1083         syslog(L_ERROR, "%s: no matches for group '%s'",
1084                LocalLogName, buf);
1085         token.type = TOKEN_EMPTY;
1086         return token;
1087     }
1088     metacycbuff = metaexprule->dest;
1089
1090     cycbuff = metacycbuff->members[metacycbuff->memb_next];  
1091     if (cycbuff == NULL) {
1092         SMseterror(SMERR_INTERNAL, "no cycbuff found");
1093         syslog(L_ERROR, "%s: no cycbuff found for %d", LocalLogName, metacycbuff->memb_next);
1094         token.type = TOKEN_EMPTY;
1095         return token;
1096     } else if (!SMpreopen && !CNFSinit_disks(cycbuff)) {
1097         SMseterror(SMERR_INTERNAL, "cycbuff initialization fail");
1098         syslog(L_ERROR, "%s: cycbuff '%s' initialization fail", LocalLogName, cycbuff->name);
1099         token.type = TOKEN_EMPTY;
1100         return token;
1101     }
1102
1103     /* cycbuff->free should have already been aligned by the last write, but
1104        realign it just to be sure. */
1105     tonextblock = CNFS_BLOCKSIZE - (cycbuff->free & (CNFS_BLOCKSIZE - 1));
1106     if (tonextblock != CNFS_BLOCKSIZE)
1107         cycbuff->free += tonextblock;
1108
1109     /* Article too big? */
1110     if (cycbuff->len - cycbuff->free < CNFS_BLOCKSIZE + 1)
1111         left = 0;
1112     else
1113         left = cycbuff->len - cycbuff->free - CNFS_BLOCKSIZE - 1;
1114     if (article.len > left) {
1115         for (middle = cycbuff->free ;middle < cycbuff->len - CNFS_BLOCKSIZE - 1;
1116             middle += CNFS_BLOCKSIZE) {
1117             CNFSUsedBlock(cycbuff, middle, true, false);
1118         }
1119         if (innconf->nfswriter) {
1120             cnfs_mapcntl(NULL, 0, MS_ASYNC);
1121         }
1122         cycbuff->free = cycbuff->minartoffset;
1123         cycbuff->cyclenum++;
1124         if (cycbuff->cyclenum == 0)
1125           cycbuff->cyclenum += 2;               /* cnfs_next() needs this */
1126         cycbuff->needflush = true;
1127         if (metacycbuff->metamode == INTERLEAVE) {
1128           CNFSflushhead(cycbuff);               /* Flush, just for giggles */
1129           syslog(L_NOTICE, "%s: cycbuff %s rollover to cycle 0x%x... remain calm",
1130                LocalLogName, cycbuff->name, cycbuff->cyclenum);
1131         } else {
1132           /* SEQUENTIAL */
1133           cycbuff->currentbuff = false;
1134           CNFSflushhead(cycbuff);               /* Flush, just for giggles */
1135           if (!SMpreopen) CNFSshutdowncycbuff(cycbuff);
1136           metacycbuff->memb_next = (metacycbuff->memb_next + 1) % metacycbuff->count;
1137           cycbuff = metacycbuff->members[metacycbuff->memb_next];  
1138           if (!SMpreopen && !CNFSinit_disks(cycbuff)) {
1139               SMseterror(SMERR_INTERNAL, "cycbuff initialization fail");
1140               syslog(L_ERROR, "%s: cycbuff '%s' initialization fail", LocalLogName, cycbuff->name);
1141               token.type = TOKEN_EMPTY;
1142               return token;
1143           }
1144           syslog(L_NOTICE, "%s: metacycbuff %s cycbuff is moved to %s remain calm",
1145                LocalLogName, metacycbuff->name, cycbuff->name);
1146           cycbuff->currentbuff = true;
1147           cycbuff->needflush = true;
1148           CNFSflushhead(cycbuff);               /* Flush, just for giggles */
1149         }
1150     }
1151
1152     /* Ah, at least we know all three important data */
1153     artcycbuffname = cycbuff->name;
1154     artoffset = cycbuff->free;
1155     artcyclenum = cycbuff->cyclenum;
1156
1157     memset(&cah, 0, sizeof(cah));
1158     cah.size = htonl(article.len);
1159     if (article.arrived == (time_t)0)
1160         cah.arrived = htonl(time(NULL));
1161     else
1162         cah.arrived = htonl(article.arrived);
1163     cah.class = class;
1164
1165     if (lseek(cycbuff->fd, artoffset, SEEK_SET) < 0) {
1166         SMseterror(SMERR_INTERNAL, "lseek failed");
1167         syslog(L_ERROR, "%s: lseek failed for '%s' offset 0x%s: %m",
1168                LocalLogName, cycbuff->name, CNFSofft2hex(artoffset, false));
1169         token.type = TOKEN_EMPTY;
1170         if (!SMpreopen) CNFSshutdowncycbuff(cycbuff);
1171         return token;
1172     }
1173     if (iovcnt == 0) {
1174         iov = xmalloc((article.iovcnt + 2) * sizeof(struct iovec));
1175         iovcnt = article.iovcnt + 2;
1176     } else if (iovcnt < article.iovcnt + 2) {
1177         iov = xrealloc(iov, (article.iovcnt + 2) * sizeof(struct iovec));
1178         iovcnt = article.iovcnt + 2;
1179     }
1180     iov[0].iov_base = (char *) &cah;
1181     iov[0].iov_len = sizeof(cah);
1182     totlen = iov[0].iov_len;
1183     for (i = 1; i <= article.iovcnt; i++) {
1184         iov[i].iov_base = article.iov[i-1].iov_base;
1185         iov[i].iov_len = article.iov[i-1].iov_len;
1186         totlen += iov[i].iov_len;
1187     }
1188     if ((totlen & (CNFS_BLOCKSIZE - 1)) != 0) {
1189         /* Want to xwritev an exact multiple of CNFS_BLOCKSIZE */
1190         iov[i].iov_base = alignbuf;
1191         iov[i].iov_len = CNFS_BLOCKSIZE - (totlen & (CNFS_BLOCKSIZE - 1));
1192         totlen += iov[i].iov_len;
1193         i++;
1194     }
1195     if (xwritev(cycbuff->fd, iov, i) < 0) {
1196         SMseterror(SMERR_INTERNAL, "cnfs_store() xwritev() failed");
1197         syslog(L_ERROR,
1198                "%s: cnfs_store xwritev failed for '%s' offset 0x%s: %m",
1199                LocalLogName, artcycbuffname, CNFSofft2hex(artoffset, false));
1200         token.type = TOKEN_EMPTY;
1201         if (!SMpreopen) CNFSshutdowncycbuff(cycbuff);
1202         return token;
1203     }
1204     cycbuff->needflush = true;
1205
1206     /* Now that the article is written, advance the free pointer & flush */
1207     cycbuff->free += totlen;
1208
1209     /*
1210     ** If cycbuff->free > cycbuff->len, don't worry.  The next cnfs_store()
1211     ** will detect the situation & wrap around correctly.
1212     */
1213     if (metacycbuff->metamode == INTERLEAVE)
1214       metacycbuff->memb_next = (metacycbuff->memb_next + 1) % metacycbuff->count;
1215     if (++metacycbuff->write_count % metabuff_update == 0) {
1216         for (i = 0; i < metacycbuff->count; i++) {
1217             CNFSflushhead(metacycbuff->members[i]);
1218         }
1219     }
1220     CNFSUsedBlock(cycbuff, artoffset, true, true);
1221     for (middle = artoffset + CNFS_BLOCKSIZE; middle < cycbuff->free;
1222          middle += CNFS_BLOCKSIZE) {
1223         CNFSUsedBlock(cycbuff, middle, true, false);
1224     }
1225     if (innconf->nfswriter) {
1226         cnfs_mapcntl(NULL, 0, MS_ASYNC);
1227     }
1228     if (!SMpreopen) CNFSshutdowncycbuff(cycbuff);
1229     return CNFSMakeToken(artcycbuffname, artoffset, artcyclenum, class);
1230 }
1231
1232 ARTHANDLE *cnfs_retrieve(const TOKEN token, const RETRTYPE amount) {
1233     char                cycbuffname[9];
1234     off_t               offset;
1235     uint32_t            cycnum;
1236     CYCBUFF             *cycbuff;
1237     ARTHANDLE           *art;
1238     CNFSARTHEADER       cah;
1239     PRIV_CNFS           *private;
1240     char                *p;
1241     long                pagefudge;
1242     off_t               mmapoffset;
1243     static TOKEN        ret_token;
1244     static bool         nomessage = false;
1245     int                 plusoffset = 0;
1246
1247     if (token.type != TOKEN_CNFS) {
1248         SMseterror(SMERR_INTERNAL, NULL);
1249         return NULL;
1250     }
1251     if (! CNFSBreakToken(token, cycbuffname, &offset, &cycnum)) {
1252         /* SMseterror() should have already been called */
1253         return NULL;
1254     }
1255     if ((cycbuff = CNFSgetcycbuffbyname(cycbuffname)) == NULL) {
1256         SMseterror(SMERR_NOENT, NULL);
1257         if (!nomessage) {
1258             syslog(L_ERROR, "%s: cnfs_retrieve: token %s: bogus cycbuff name: %s:0x%s:%d",
1259                LocalLogName, TokenToText(token), cycbuffname, CNFSofft2hex(offset, false), cycnum);
1260             nomessage = true;
1261         }
1262         return NULL;
1263     }
1264     if (!SMpreopen && !CNFSinit_disks(cycbuff)) {
1265         SMseterror(SMERR_INTERNAL, "cycbuff initialization fail");
1266         syslog(L_ERROR, "%s: cycbuff '%s' initialization fail", LocalLogName, cycbuff->name);
1267         return NULL;
1268     }
1269     if (! CNFSArtMayBeHere(cycbuff, offset, cycnum)) {
1270         SMseterror(SMERR_NOENT, NULL);
1271         if (!SMpreopen) CNFSshutdowncycbuff(cycbuff);
1272         return NULL;
1273     }
1274
1275     art = xmalloc(sizeof(ARTHANDLE));
1276     art->type = TOKEN_CNFS;
1277     if (amount == RETR_STAT) {
1278         art->data = NULL;
1279         art->len = 0;
1280         art->private = NULL;
1281         ret_token = token;    
1282         art->token = &ret_token;
1283         if (!SMpreopen) CNFSshutdowncycbuff(cycbuff);
1284         return art;
1285     }
1286     /*
1287     ** Because we don't know the length of the article (yet), we'll
1288     ** just mmap() a chunk of memory which is guaranteed to be larger
1289     ** than the largest article can be.
1290     ** XXX Because the max article size can be changed, we could get into hot
1291     ** XXX water here.  So, to be safe, we double MAX_ART_SIZE and add enough
1292     ** XXX extra for the pagesize fudge factor and CNFSARTHEADER structure.
1293     */
1294     if (pread(cycbuff->fd, &cah, sizeof(cah), offset) != sizeof(cah)) {
1295         SMseterror(SMERR_UNDEFINED, "read failed");
1296         syslog(L_ERROR, "%s: could not read token %s %s:0x%s:%d: %m",
1297                 LocalLogName, TokenToText(token), cycbuffname, CNFSofft2hex(offset, false), cycnum);
1298         free(art);
1299         if (!SMpreopen) CNFSshutdowncycbuff(cycbuff);
1300         return NULL;
1301     }
1302 #ifdef OLD_CNFS
1303     if(cah.size == htonl(0x1234) && ntohl(cah.arrived) < time(NULL)-10*365*24*3600) {
1304         oldCNFSARTHEADER cahh;
1305         *(CNFSARTHEADER *)&cahh = cah;
1306         if(pread(cycbuff->fd, ((char *)&cahh)+sizeof(CNFSARTHEADER), sizeof(oldCNFSARTHEADER)-sizeof(CNFSARTHEADER), offset+sizeof(cah)) != sizeof(oldCNFSARTHEADER)-sizeof(CNFSARTHEADER)) {
1307             SMseterror(SMERR_UNDEFINED, "read2 failed");
1308             syslog(L_ERROR, "%s: could not read2 token %s %s:0x%s:%ld: %m",
1309                     LocalLogName, TokenToText(token), cycbuffname,
1310                     CNFSofft2hex(offset, false), cycnum);
1311             free(art);
1312             if (!SMpreopen) CNFSshutdowncycbuff(cycbuff);
1313             return NULL;
1314         }
1315         cah.size = cahh.size;
1316         cah.arrived = htonl(time(NULL));
1317         cah.class = 0;
1318         plusoffset = sizeof(oldCNFSARTHEADER)-sizeof(CNFSARTHEADER);
1319     }
1320 #endif /* OLD_CNFS */
1321     if (offset > cycbuff->len - CNFS_BLOCKSIZE - (off_t) ntohl(cah.size) - 1) {
1322         if (!SMpreopen) {
1323             SMseterror(SMERR_UNDEFINED, "CNFSARTHEADER size overflow");
1324             syslog(L_ERROR, "%s: could not match article size token %s %s:0x%s:%d: %d",
1325                 LocalLogName, TokenToText(token), cycbuffname, CNFSofft2hex(offset, false), cycnum, ntohl(cah.size));
1326             free(art);
1327             CNFSshutdowncycbuff(cycbuff);
1328             return NULL;
1329         }
1330         CNFSReadFreeAndCycle(cycbuff);
1331         if (offset > cycbuff->len - CNFS_BLOCKSIZE - (off_t) ntohl(cah.size) - 1) {
1332             SMseterror(SMERR_UNDEFINED, "CNFSARTHEADER size overflow");
1333             syslog(L_ERROR, "%s: could not match article size token %s %s:0x%s:%d: %d",
1334                 LocalLogName, TokenToText(token), cycbuffname, CNFSofft2hex(offset, false), cycnum, ntohl(cah.size));
1335             free(art);
1336             return NULL;
1337         }
1338     }
1339     /* checking the bitmap to ensure cah.size is not broken was dropped */
1340     if (innconf->cnfscheckfudgesize != 0 && innconf->maxartsize != 0 &&
1341         (ntohl(cah.size) > (size_t) innconf->maxartsize + innconf->cnfscheckfudgesize)) {
1342         char buf1[24];
1343         strlcpy(buf1, CNFSofft2hex(cycbuff->free, false), sizeof(buf1));
1344         SMseterror(SMERR_UNDEFINED, "CNFSARTHEADER fudge size overflow");
1345         syslog(L_ERROR, "%s: fudge size overflows bitmaps %s %s:0x%s: %u",
1346         LocalLogName, TokenToText(token), cycbuffname, buf1, ntohl(cah.size));
1347         if (!SMpreopen) CNFSshutdowncycbuff(cycbuff);
1348         free(art);
1349         return NULL;
1350     }
1351     private = xmalloc(sizeof(PRIV_CNFS));
1352     art->private = (void *)private;
1353     art->arrived = ntohl(cah.arrived);
1354     offset += sizeof(cah) + plusoffset;
1355     if (innconf->articlemmap) {
1356         pagefudge = offset % pagesize;
1357         mmapoffset = offset - pagefudge;
1358         private->len = pagefudge + ntohl(cah.size);
1359         if ((private->base = mmap(NULL, private->len, PROT_READ,
1360                 MAP_SHARED, cycbuff->fd, mmapoffset)) == MAP_FAILED) {
1361             SMseterror(SMERR_UNDEFINED, "mmap failed");
1362             syslog(L_ERROR, "%s: could not mmap token %s %s:0x%s:%d: %m",
1363                 LocalLogName, TokenToText(token), cycbuffname, CNFSofft2hex(offset, false), cycnum);
1364             free(art->private);
1365             free(art);
1366             if (!SMpreopen) CNFSshutdowncycbuff(cycbuff);
1367             return NULL;
1368         }
1369         mmap_invalidate(private->base, private->len);
1370         if (amount == RETR_ALL)
1371             madvise(private->base, private->len, MADV_WILLNEED);
1372         else
1373             madvise(private->base, private->len, MADV_SEQUENTIAL);
1374     } else {
1375         private->base = xmalloc(ntohl(cah.size));
1376         pagefudge = 0;
1377         if (pread(cycbuff->fd, private->base, ntohl(cah.size), offset) < 0) {
1378             SMseterror(SMERR_UNDEFINED, "read failed");
1379             syslog(L_ERROR, "%s: could not read token %s %s:0x%s:%d: %m",
1380                 LocalLogName, TokenToText(token), cycbuffname, CNFSofft2hex(offset, false), cycnum);
1381             free(private->base);
1382             free(art->private);
1383             free(art);
1384             if (!SMpreopen) CNFSshutdowncycbuff(cycbuff);
1385             return NULL;
1386         }
1387     }
1388     ret_token = token;    
1389     art->token = &ret_token;
1390     art->len = ntohl(cah.size);
1391     if (amount == RETR_ALL) {
1392         art->data = innconf->articlemmap ? private->base + pagefudge : private->base;
1393         if (!SMpreopen) CNFSshutdowncycbuff(cycbuff);
1394         return art;
1395     }
1396     if ((p = wire_findbody(innconf->articlemmap ? private->base + pagefudge : private->base, art->len)) == NULL) {
1397         SMseterror(SMERR_NOBODY, NULL);
1398         if (innconf->articlemmap)
1399             munmap(private->base, private->len);
1400         else
1401             free(private->base);
1402         free(art->private);
1403         free(art);
1404         if (!SMpreopen) CNFSshutdowncycbuff(cycbuff);
1405         return NULL;
1406     }
1407     if (amount == RETR_HEAD) {
1408         if (innconf->articlemmap) {
1409             art->data = private->base + pagefudge;
1410             art->len = p - private->base - pagefudge;
1411         } else {
1412             art->data = private->base;
1413             art->len = p - private->base;
1414         }
1415         if (!SMpreopen) CNFSshutdowncycbuff(cycbuff);
1416         return art;
1417     }
1418     if (amount == RETR_BODY) {
1419         art->data = p;
1420         if (innconf->articlemmap)
1421             art->len = art->len - (p - private->base - pagefudge);
1422         else
1423             art->len = art->len - (p - private->base);
1424         if (!SMpreopen) CNFSshutdowncycbuff(cycbuff);
1425         return art;
1426     }
1427     SMseterror(SMERR_UNDEFINED, "Invalid retrieve request");
1428     if (innconf->articlemmap)
1429         munmap(private->base, private->len);
1430     else
1431         free(private->base);
1432     free(art->private);
1433     free(art);
1434     if (!SMpreopen) CNFSshutdowncycbuff(cycbuff);
1435     return NULL;
1436 }
1437
1438 void cnfs_freearticle(ARTHANDLE *article) {
1439     PRIV_CNFS   *private;
1440
1441     if (!article)
1442         return;
1443     
1444     if (article->private) {
1445         private = (PRIV_CNFS *)article->private;
1446         if (innconf->articlemmap)
1447             munmap(private->base, private->len);
1448         else
1449             free(private->base);
1450         free(private);
1451     }
1452     free(article);
1453 }
1454
1455 bool cnfs_cancel(TOKEN token) {
1456     char                cycbuffname[9];
1457     off_t               offset;
1458     uint32_t            cycnum;
1459     CYCBUFF             *cycbuff;
1460
1461     if (token.type != TOKEN_CNFS) {
1462         SMseterror(SMERR_INTERNAL, NULL);
1463         return false;
1464     }
1465     if (! CNFSBreakToken(token, cycbuffname, &offset, &cycnum)) {
1466         SMseterror(SMERR_INTERNAL, NULL);
1467         /* SMseterror() should have already been called */
1468         return false;
1469     }
1470     if ((cycbuff = CNFSgetcycbuffbyname(cycbuffname)) == NULL) {
1471         SMseterror(SMERR_INTERNAL, "bogus cycbuff name");
1472         return false;
1473     }
1474     if (!SMpreopen && !CNFSinit_disks(cycbuff)) {
1475         SMseterror(SMERR_INTERNAL, "cycbuff initialization fail");
1476         syslog(L_ERROR, "%s: cycbuff '%s' initialization fail", LocalLogName, cycbuff->name);
1477         return false;
1478     }
1479     if (! (cycnum == cycbuff->cyclenum ||
1480         (cycnum == cycbuff->cyclenum - 1 && offset > cycbuff->free) ||
1481         (cycnum + 1 == 0 && cycbuff->cyclenum == 2 && offset > cycbuff->free))) {
1482         SMseterror(SMERR_NOENT, NULL);
1483         if (!SMpreopen) CNFSshutdowncycbuff(cycbuff);
1484         return false;
1485     }
1486     if (CNFSUsedBlock(cycbuff, offset, false, false) == 0) {
1487         SMseterror(SMERR_NOENT, NULL);
1488         if (!SMpreopen) CNFSshutdowncycbuff(cycbuff);
1489         return false;
1490     }
1491     CNFSUsedBlock(cycbuff, offset, true, false);
1492     if (innconf->nfswriter) {
1493         cnfs_mapcntl(NULL, 0, MS_ASYNC);
1494     }
1495     if (!SMpreopen) CNFSshutdowncycbuff(cycbuff);
1496     return true;
1497 }
1498
1499 ARTHANDLE *cnfs_next(const ARTHANDLE *article, const RETRTYPE amount) {
1500     ARTHANDLE           *art;
1501     CYCBUFF             *cycbuff;
1502     PRIV_CNFS           priv, *private;
1503     off_t               middle = 0, limit;
1504     CNFSARTHEADER       cah;
1505     off_t               offset;
1506     long                pagefudge, blockfudge;
1507     static TOKEN        token;
1508     int                 tonextblock;
1509     off_t               mmapoffset;
1510     char                *p;
1511     int                 plusoffset = 0;
1512
1513     if (article == (ARTHANDLE *)NULL) {
1514         if ((cycbuff = cycbufftab) == (CYCBUFF *)NULL)
1515             return (ARTHANDLE *)NULL;
1516         priv.offset = 0;
1517         priv.rollover = false;
1518     } else {        
1519         priv = *(PRIV_CNFS *)article->private;
1520         free(article->private);
1521         free((void *)article);
1522         if (innconf->articlemmap)
1523             munmap(priv.base, priv.len);
1524         else {
1525             /* In the case we return art->data = NULL, we
1526              * must not free an already stale pointer.
1527                -mibsoft@mibsoftware.com
1528              */
1529             if (priv.base) {
1530                 free(priv.base);
1531                 priv.base = 0;
1532             }
1533         }
1534         cycbuff = priv.cycbuff;
1535     }
1536
1537     for (;cycbuff != (CYCBUFF *)NULL;
1538             cycbuff = cycbuff->next,
1539             priv.offset = 0) {
1540
1541         if (!SMpreopen && !CNFSinit_disks(cycbuff)) {
1542             SMseterror(SMERR_INTERNAL, "cycbuff initialization fail");
1543             continue;
1544         }
1545         if (priv.rollover && priv.offset >= cycbuff->free) {
1546             priv.offset = 0;
1547             if (!SMpreopen) CNFSshutdowncycbuff(cycbuff);
1548             continue;
1549         }
1550         if (priv.offset == 0) {
1551             if (cycbuff->cyclenum == 1) {
1552                 priv.offset = cycbuff->minartoffset;
1553                 priv.rollover = true;
1554             } else {
1555                 priv.offset = cycbuff->free;
1556                 priv.rollover = false;
1557             }
1558         }
1559         if (!priv.rollover) {
1560             for (middle = priv.offset ;middle < cycbuff->len - CNFS_BLOCKSIZE - 1;
1561                 middle += CNFS_BLOCKSIZE) {
1562                 if (CNFSUsedBlock(cycbuff, middle, false, false) != 0)
1563                     break;
1564             }
1565             if (middle >= cycbuff->len - CNFS_BLOCKSIZE - 1) {
1566                 priv.rollover = true;
1567                 middle = cycbuff->minartoffset;
1568             }
1569             break;
1570         } else {
1571             for (middle = priv.offset ;middle < cycbuff->free;
1572                 middle += CNFS_BLOCKSIZE) {
1573                 if (CNFSUsedBlock(cycbuff, middle, false, false) != 0)
1574                     break;
1575             }
1576             if (middle >= cycbuff->free) {
1577                 middle = 0;
1578                 if (!SMpreopen) CNFSshutdowncycbuff(cycbuff);
1579                 continue;
1580             } else
1581                 break;
1582         }
1583     }
1584     if (cycbuff == (CYCBUFF *)NULL)
1585         return (ARTHANDLE *)NULL;
1586
1587     offset = middle;
1588     if (pread(cycbuff->fd, &cah, sizeof(cah), offset) != sizeof(cah)) {
1589         if (!SMpreopen) CNFSshutdowncycbuff(cycbuff);
1590         return (ARTHANDLE *)NULL;
1591     }
1592 #ifdef OLD_CNFS
1593     if(cah.size == htonl(0x1234) && ntohl(cah.arrived) < time(NULL)-10*365*24*3600) {
1594         oldCNFSARTHEADER cahh;
1595         *(CNFSARTHEADER *)&cahh = cah;
1596         if(pread(cycbuff->fd, ((char *)&cahh)+sizeof(CNFSARTHEADER), sizeof(oldCNFSARTHEADER)-sizeof(CNFSARTHEADER), offset+sizeof(cah)) != sizeof(oldCNFSARTHEADER)-sizeof(CNFSARTHEADER)) {
1597             if (!SMpreopen) CNFSshutdowncycbuff(cycbuff);
1598             return (ARTHANDLE *)NULL;
1599         }
1600         cah.size = cahh.size;
1601         cah.arrived = htonl(time(NULL));
1602         cah.class = 0;
1603         plusoffset = sizeof(oldCNFSARTHEADER)-sizeof(CNFSARTHEADER);
1604     }
1605 #endif /* OLD_CNFS */
1606     art = xmalloc(sizeof(ARTHANDLE));
1607     private = xmalloc(sizeof(PRIV_CNFS));
1608     art->private = (void *)private;
1609     art->type = TOKEN_CNFS;
1610     *private = priv;
1611     private->cycbuff = cycbuff;
1612     private->offset = middle;
1613     if (cycbuff->len - cycbuff->free < (off_t) ntohl(cah.size) + CNFS_BLOCKSIZE + 1) {
1614         private->offset += CNFS_BLOCKSIZE;
1615         art->data = NULL;
1616         art->len = 0;
1617         art->token = NULL;
1618         if (!SMpreopen) CNFSshutdowncycbuff(cycbuff);
1619         return art;
1620     }
1621     /* check the bitmap to ensure cah.size is not broken */
1622     blockfudge = (sizeof(cah) + plusoffset + ntohl(cah.size)) % CNFS_BLOCKSIZE;
1623     limit = private->offset + sizeof(cah) + plusoffset + ntohl(cah.size) - blockfudge + CNFS_BLOCKSIZE;
1624     if (offset < cycbuff->free) {
1625         for (middle = offset + CNFS_BLOCKSIZE; (middle < cycbuff->free) && (middle < limit);
1626             middle += CNFS_BLOCKSIZE) {
1627             if (CNFSUsedBlock(cycbuff, middle, false, false) != 0)
1628                 /* Bitmap set.  This article assumes to be broken */
1629                 break;
1630         }
1631         if ((middle > cycbuff->free) || (middle != limit)) {
1632             private->offset = middle;
1633             art->data = NULL;
1634             art->len = 0;
1635             art->token = NULL;
1636             if (!SMpreopen) CNFSshutdowncycbuff(cycbuff);
1637             return art;
1638         }
1639     } else {
1640         for (middle = offset + CNFS_BLOCKSIZE; (middle < cycbuff->len) && (middle < limit);
1641             middle += CNFS_BLOCKSIZE) {
1642             if (CNFSUsedBlock(cycbuff, middle, false, false) != 0)
1643                 /* Bitmap set.  This article assumes to be broken */
1644                 break;
1645         }
1646         if ((middle >= cycbuff->len) || (middle != limit)) {
1647             private->offset = middle;
1648             art->data = NULL;
1649             art->len = 0;
1650             art->token = NULL;
1651             if (!SMpreopen) CNFSshutdowncycbuff(cycbuff);
1652             return art;
1653         }
1654     }
1655     if (innconf->cnfscheckfudgesize != 0 && innconf->maxartsize != 0 &&
1656         ((off_t) ntohl(cah.size) > innconf->maxartsize + innconf->cnfscheckfudgesize)) {
1657         art->data = NULL;
1658         art->len = 0;
1659         art->token = NULL;
1660         private->base = 0;
1661         if (!SMpreopen) CNFSshutdowncycbuff(cycbuff);
1662         return art;
1663     }
1664
1665     private->offset += (off_t) ntohl(cah.size) + sizeof(cah) + plusoffset;
1666     tonextblock = CNFS_BLOCKSIZE - (private->offset & (CNFS_BLOCKSIZE - 1));
1667     private->offset += (off_t) tonextblock;
1668     art->arrived = ntohl(cah.arrived);
1669     token = CNFSMakeToken(cycbuff->name, offset, (offset > cycbuff->free) ? cycbuff->cyclenum - 1 : cycbuff->cyclenum, cah.class);
1670     art->token = &token;
1671     offset += sizeof(cah) + plusoffset;
1672     if (innconf->articlemmap) {
1673         pagefudge = offset % pagesize;
1674         mmapoffset = offset - pagefudge;
1675         private->len = pagefudge + ntohl(cah.size);
1676         if ((private->base = mmap(0, private->len, PROT_READ,
1677             MAP_SHARED, cycbuff->fd, mmapoffset)) == MAP_FAILED) {
1678             art->data = NULL;
1679             art->len = 0;
1680             art->token = NULL;
1681             if (!SMpreopen) CNFSshutdowncycbuff(cycbuff);
1682             return art;
1683         }
1684         mmap_invalidate(private->base, private->len);
1685         madvise(private->base, private->len, MADV_SEQUENTIAL);
1686     } else {
1687         private->base = xmalloc(ntohl(cah.size));
1688         pagefudge = 0;
1689         if (pread(cycbuff->fd, private->base, ntohl(cah.size), offset) < 0) {
1690             art->data = NULL;
1691             art->len = 0;
1692             art->token = NULL;
1693             if (!SMpreopen) CNFSshutdowncycbuff(cycbuff);
1694             free(private->base);
1695             private->base = 0;
1696             return art;
1697         }
1698     }
1699     art->len = ntohl(cah.size);
1700     if (amount == RETR_ALL) {
1701         art->data = innconf->articlemmap ? private->base + pagefudge : private->base;
1702         if (!SMpreopen) CNFSshutdowncycbuff(cycbuff);
1703         return art;
1704     }
1705     if ((p = wire_findbody(innconf->articlemmap ? private->base + pagefudge : private->base, art->len)) == NULL) {
1706         art->data = NULL;
1707         art->len = 0;
1708         art->token = NULL;
1709         if (!SMpreopen) CNFSshutdowncycbuff(cycbuff);
1710         return art;
1711     }
1712     if (amount == RETR_HEAD) {
1713         if (innconf->articlemmap) {
1714             art->data = private->base + pagefudge;
1715             art->len = p - private->base - pagefudge;
1716         } else {
1717             art->data = private->base;
1718             art->len = p - private->base;
1719         }
1720         if (!SMpreopen) CNFSshutdowncycbuff(cycbuff);
1721         return art;
1722     }
1723     if (amount == RETR_BODY) {
1724         art->data = p;
1725         if (innconf->articlemmap)
1726             art->len = art->len - (p - private->base - pagefudge);
1727         else
1728             art->len = art->len - (p - private->base);
1729         if (!SMpreopen) CNFSshutdowncycbuff(cycbuff);
1730         return art;
1731     }
1732     art->data = NULL;
1733     art->len = 0;
1734     art->token = NULL;
1735     if (!SMpreopen) CNFSshutdowncycbuff(cycbuff);
1736     return art;
1737 }
1738
1739 bool cnfs_ctl(PROBETYPE type, TOKEN *token UNUSED, void *value) {
1740     struct artngnum *ann;
1741
1742     switch (type) {
1743     case SMARTNGNUM:    
1744         if ((ann = (struct artngnum *)value) == NULL)
1745             return false;
1746         /* make SMprobe() call cnfs_retrieve() */
1747         ann->artnum = 0;
1748         return true; 
1749     default:
1750         return false; 
1751     }   
1752 }
1753
1754 bool cnfs_flushcacheddata(FLUSHTYPE type) {
1755     if (type == SM_ALL || type == SM_HEAD)
1756         CNFSflushallheads();
1757     return true; 
1758 }
1759
1760 void
1761 cnfs_printfiles(FILE *file, TOKEN token, char **xref UNUSED,
1762                 int ngroups UNUSED)
1763 {
1764     fprintf(file, "%s\n", TokenToText(token));
1765 }
1766
1767 void cnfs_shutdown(void) {
1768     CNFScleancycbuff();
1769     CNFScleanmetacycbuff();
1770     CNFScleanexpirerule();
1771 }