1 /* $Id: timecaf.c 7412 2005-10-09 03:44:35Z eagle $
3 ** Like the timehash storage method (and heavily inspired by it), but uses
4 ** the CAF library to store multiple articles in a single file.
9 #include "portable/mmap.h"
19 #include "inn/innconf.h"
26 /* Needed for htonl() and friends on AIX 4.1. */
27 #include <netinet/in.h>
30 char *artdata; /* start of the article data -- may be mmaped */
31 char *mmapbase; /* actual start of mmaped region (on pagesize bndry, not necessarily == artdaya */
32 unsigned int artlen; /* art length. */
33 size_t mmaplen; /* length of mmap region. */
34 DIR *top; /* open handle on top level dir. */
35 DIR *sec; /* open handle on the 2nd level directory */
36 DIR *ter; /* open handle on 3rd level dir. */
37 struct dirent *topde; /* last entry we got from top */
38 struct dirent *secde; /* last entry we got from sec */
39 struct dirent *terde; /* last entry we got from sec */
45 /* current path/fd for an open CAF file */
47 char *path; /* path to file. */
48 int fd; /* open fd -- -1 if no file currently open. */
51 static CAFOPENFILE ReadingFile, WritingFile;
52 static char *DeletePath;
53 static ARTNUM *DeleteArtnums;
54 static unsigned int NumDeleteArtnums, MaxDeleteArtnums;
56 typedef enum {FIND_DIR, FIND_CAF, FIND_TOPDIR} FINDTYPE;
59 ** Structures for the cache for stat information (to make expireover etc.
62 ** The first structure contains the TOC info for a single CAF file. The 2nd
63 ** one has pointers to the info for up to 256 CAF files, indexed
64 ** by the 2nd least significant byte of the arrival time.
67 struct caftoccacheent {
71 typedef struct caftoccacheent CAFTOCCACHEENT;
73 struct caftocl1cache {
74 CAFTOCCACHEENT *entries[256];
76 typedef struct caftocl1cache CAFTOCL1CACHE;
79 ** and similar structures indexed by the 3rd and 4th bytes of the arrival time.
80 ** pointing to the lower level structures. Note that the top level structure
81 ** (the one indexed by the MSByte of the timestamp) is likely to have only
82 ** one active pointer, unless your spool keeps more than 194 days of articles,
83 ** but it doesn't cost much to keep that one structure around and keep the
87 struct caftocl2cache {
88 CAFTOCL1CACHE *l1ptr[256];
90 typedef struct caftocl2cache CAFTOCL2CACHE;
92 struct caftocl3cache {
93 CAFTOCL2CACHE *l2ptr[256];
95 typedef struct caftocl3cache CAFTOCL3CACHE;
97 static CAFTOCL3CACHE *TOCCache[256]; /* indexed by storage class! */
98 static int TOCCacheHits, TOCCacheMisses;
101 static TOKEN MakeToken(time_t now, int seqnum, STORAGECLASS class, TOKEN *oldtoken) {
106 if (oldtoken == (TOKEN *)NULL)
107 memset(&token, '\0', sizeof(token));
109 memcpy(&token, oldtoken, sizeof(token));
110 token.type = TOKEN_TIMECAF;
113 memcpy(token.token, &i, sizeof(i));
115 memmove(token.token, &token.token[sizeof(i) - 4], 4);
117 memcpy(&token.token[4], &s + (sizeof(s) - 2), 2);
122 static void BreakToken(TOKEN token, int *now, int *seqnum) {
124 unsigned short s = 0;
126 memcpy(&i, token.token, sizeof(i));
127 memcpy(&s, &token.token[4], sizeof(s));
129 *seqnum = (int)ntohs(s);
133 ** Note: the time here is really "time>>8", i.e. a timestamp that's been
134 ** shifted right by 8 bits.
136 static char *MakePath(int now, const STORAGECLASS class) {
140 /* innconf->patharticles + '/timecaf-zz/xx/xxxx.CF' */
141 length = strlen(innconf->patharticles) + 32;
142 path = xmalloc(length);
143 snprintf(path, length, "%s/timecaf-%02x/%02x/%02x%02x.CF",
144 innconf->patharticles, class,
145 (now >> 8) & 0xff, (now >> 16) & 0xff, now & 0xff);
150 static TOKEN *PathNumToToken(char *path, ARTNUM artnum) {
152 unsigned int t1, t2, class;
153 unsigned int timestamp;
156 n = sscanf(path, "timecaf-%02x/%02x/%04x.CF", &class, &t1, &t2);
158 return (TOKEN *)NULL;
159 timestamp = ((t1 << 8) & 0xff00) | ((t2 << 8) & 0xff0000) | ((t2 << 0) & 0xff);
160 token = MakeToken(timestamp, artnum, class, (TOKEN *)NULL);
165 bool timecaf_init(SMATTRIBUTE *attr) {
167 syslog(L_ERROR, "timecaf: attr is NULL");
168 SMseterror(SMERR_INTERNAL, "attr is NULL");
171 attr->selfexpire = false;
172 attr->expensivestat = false;
173 if (STORAGE_TOKEN_LENGTH < 6) {
174 syslog(L_FATAL, "timecaf: token length is less than 6 bytes");
175 SMseterror(SMERR_TOKENSHORT, NULL);
178 ReadingFile.fd = WritingFile.fd = -1;
179 ReadingFile.path = WritingFile.path = (char *)NULL;
184 ** Routines for managing the 'TOC cache' (cache of TOCs of various CAF files)
186 ** Attempt to look up a given TOC entry in the cache. Takes the timestamp
190 static CAFTOCCACHEENT *
191 CheckTOCCache(int timestamp, int tokenclass)
195 CAFTOCCACHEENT *cent;
198 if (TOCCache[tokenclass] == NULL) return NULL; /* cache is empty */
200 tmp = (timestamp>>16) & 0xff;
201 l2 = TOCCache[tokenclass]->l2ptr[tmp];
202 if (l2 == NULL) return NULL;
204 tmp = (timestamp>>8) & 0xff;
206 if (l1 == NULL) return NULL;
208 tmp = (timestamp) & 0xff;
209 cent = l1->entries[tmp];
216 ** Add given TOC and header to the cache. Assume entry is not already in
219 static CAFTOCCACHEENT *
220 AddTOCCache(int timestamp, CAFTOCENT *toc, CAFHEADER head, int tokenclass)
224 CAFTOCCACHEENT *cent;
228 if (TOCCache[tokenclass] == NULL) {
229 TOCCache[tokenclass] = xmalloc(sizeof(CAFTOCL3CACHE));
230 for (i = 0 ; i < 256 ; ++i) TOCCache[tokenclass]->l2ptr[i] = NULL;
233 tmp = (timestamp>>16) & 0xff;
234 l2 = TOCCache[tokenclass]->l2ptr[tmp];
236 TOCCache[tokenclass]->l2ptr[tmp] = l2 = xmalloc(sizeof(CAFTOCL2CACHE));
237 for (i = 0 ; i < 256 ; ++i) l2->l1ptr[i] = NULL;
240 tmp = (timestamp>>8) & 0xff;
243 l2->l1ptr[tmp] = l1 = xmalloc(sizeof(CAFTOCL1CACHE));
244 for (i = 0 ; i < 256 ; ++i) l1->entries[i] = NULL;
247 tmp = (timestamp) & 0xff;
248 cent = xmalloc(sizeof(CAFTOCCACHEENT));
249 l1->entries[tmp] = cent;
258 ** Do stating of an article, going thru the TOC cache if possible.
262 StatArticle(int timestamp, ARTNUM artnum, int tokenclass)
264 CAFTOCCACHEENT *cent;
271 cent = CheckTOCCache(timestamp,tokenclass);
273 path = MakePath(timestamp, tokenclass);
274 toc = CAFReadTOC(path, &head);
276 if (caf_error == CAF_ERR_ARTNOTHERE) {
277 SMseterror(SMERR_NOENT, NULL);
279 SMseterror(SMERR_UNDEFINED, NULL);
284 cent = AddTOCCache(timestamp, toc, head, tokenclass);
288 /* check current TOC for the given artnum. */
289 if (artnum < cent->header.Low || artnum > cent->header.High) {
290 SMseterror(SMERR_NOENT, NULL);
294 tocentry = &(cent->toc[artnum - cent->header.Low]);
295 if (tocentry->Size == 0) {
296 /* no article with that article number present */
297 SMseterror(SMERR_NOENT, NULL);
301 /* stat is a success, so build a null art struct to represent that. */
302 art = xmalloc(sizeof(ARTHANDLE));
303 art->type = TOKEN_TIMECAF;
312 CloseOpenFile(CAFOPENFILE *foo) {
321 TOKEN timecaf_store(const ARTHANDLE article, const STORAGECLASS class) {
331 if (article.arrived == (time_t)0)
334 now = article.arrived;
337 art = 0; /* magic: 0=="next available article number. */
339 path = MakePath(timestamp, class);
340 /* check to see if we have this CAF file already open. */
341 if (WritingFile.fd < 0 || strcmp(WritingFile.path, path) != 0) {
342 /* we're writing to a different file, close old one and start new one. */
343 CloseOpenFile(&WritingFile);
344 fd = CAFOpenArtWrite(path, &art, true, article.len);
346 if (caf_error == CAF_ERR_IO && caf_errno == ENOENT) {
347 /* directories in the path don't exist, try creating them. */
348 p = strrchr(path, '/');
350 if (!MakeDirectory(path, true)) {
351 syslog(L_ERROR, "timecaf: could not make directory %s %m", path);
352 token.type = TOKEN_EMPTY;
354 SMseterror(SMERR_UNDEFINED, NULL);
358 fd = CAFOpenArtWrite(path, &art, true, article.len);
360 syslog(L_ERROR, "timecaf: could not OpenArtWrite %s/%ld, %s", path, art, CAFErrorStr());
361 SMseterror(SMERR_UNDEFINED, NULL);
363 token.type = TOKEN_EMPTY;
368 syslog(L_ERROR, "timecaf: could not OpenArtWrite %s/%ld, %s", path, art, CAFErrorStr());
369 SMseterror(SMERR_UNDEFINED, NULL);
371 token.type = TOKEN_EMPTY;
376 /* can reuse existing fd, assuming all goes well. */
379 /* nuke extraneous copy of path to avoid mem leaks. */
381 path = WritingFile.path;
383 if (CAFStartWriteFd(fd, &art, article.len) < 0) {
384 syslog(L_ERROR, "timecaf: could not OpenArtWrite %s/%ld, %s", path, art, CAFErrorStr());
385 SMseterror(SMERR_UNDEFINED, NULL);
387 token.type = TOKEN_EMPTY;
392 WritingFile.path = path;
393 close_on_exec(fd, true);
394 result = xwritev(fd, article.iov, article.iovcnt);
395 if (result != (ssize_t) article.len) {
396 SMseterror(SMERR_UNDEFINED, NULL);
397 syslog(L_ERROR, "timecaf error writing %s %m", path);
398 token.type = TOKEN_EMPTY;
399 CloseOpenFile(&WritingFile);
402 if (CAFFinishArtWrite(fd) < 0) {
403 SMseterror(SMERR_UNDEFINED, NULL);
404 syslog(L_ERROR, "timecaf error writing %s %s", path, CAFErrorStr());
405 token.type = TOKEN_EMPTY;
406 CloseOpenFile(&WritingFile);
410 return MakeToken(timestamp, art, class, article.token);
413 /* Get a handle to article artnum in CAF-file path. */
414 static ARTHANDLE *OpenArticle(const char *path, ARTNUM artnum, const RETRTYPE amount) {
416 PRIV_TIMECAF *private;
420 static long pagesize = 0;
423 pagesize = getpagesize();
425 syslog(L_ERROR, "timecaf getpagesize failed: %m");
431 /* XXX need to figure some way to cache open fds or something? */
432 if ((fd = CAFOpenArtRead((char *)path, artnum, &len)) < 0) {
433 if (caf_error == CAF_ERR_ARTNOTHERE) {
434 SMseterror(SMERR_NOENT, NULL);
436 SMseterror(SMERR_UNDEFINED, NULL);
441 art = xmalloc(sizeof(ARTHANDLE));
442 art->type = TOKEN_TIMECAF;
444 if (amount == RETR_STAT) {
452 private = xmalloc(sizeof(PRIV_TIMECAF));
453 art->private = (void *)private;
454 private->artlen = len;
455 if (innconf->articlemmap) {
456 off_t curoff, tmpoff;
459 curoff = lseek(fd, (off_t) 0, SEEK_CUR);
460 delta = curoff % pagesize;
461 tmpoff = curoff - delta;
462 private->mmaplen = len + delta;
463 if ((private->mmapbase = mmap(NULL, private->mmaplen, PROT_READ, MAP_SHARED, fd, tmpoff)) == MAP_FAILED) {
464 SMseterror(SMERR_UNDEFINED, NULL);
465 syslog(L_ERROR, "timecaf: could not mmap article: %m");
470 mmap_invalidate(private->mmapbase, private->mmaplen);
471 if (amount == RETR_ALL)
472 madvise(private->mmapbase, private->mmaplen, MADV_WILLNEED);
474 madvise(private->mmapbase, private->mmaplen, MADV_SEQUENTIAL);
475 private->artdata = private->mmapbase + delta;
477 private->artdata = xmalloc(private->artlen);
478 if (read(fd, private->artdata, private->artlen) < 0) {
479 SMseterror(SMERR_UNDEFINED, NULL);
480 syslog(L_ERROR, "timecaf: could not read article: %m");
481 free(private->artdata);
492 private->curtoc = NULL;
493 private->curartnum = 0;
494 private->topde = NULL;
495 private->secde = NULL;
496 private->terde = NULL;
498 if (amount == RETR_ALL) {
499 art->data = private->artdata;
500 art->len = private->artlen;
504 if ((p = wire_findbody(private->artdata, private->artlen)) == NULL) {
505 SMseterror(SMERR_NOBODY, NULL);
506 if (innconf->articlemmap)
507 munmap(private->mmapbase, private->mmaplen);
509 free(private->artdata);
515 if (amount == RETR_HEAD) {
516 art->data = private->artdata;
517 art->len = p - private->artdata;
521 if (amount == RETR_BODY) {
523 art->len = art->len - (private->artdata - p - 4);
526 SMseterror(SMERR_UNDEFINED, "Invalid retrieve request");
527 if (innconf->articlemmap)
528 munmap(private->mmapbase, private->mmaplen);
530 free(private->artdata);
536 ARTHANDLE *timecaf_retrieve(const TOKEN token, const RETRTYPE amount) {
541 static TOKEN ret_token;
544 if (token.type != TOKEN_TIMECAF) {
545 SMseterror(SMERR_INTERNAL, NULL);
549 BreakToken(token, ×tamp, &artnum);
552 ** Do a possible shortcut on RETR_STAT requests, going thru the "TOC cache"
553 ** we mentioned above. We only try to go thru the TOC Cache under these
555 ** 1) SMpreopen is true (so we're "preopening" the TOCs.)
556 ** 2) the timestamp is older than the timestamp corresponding to current
557 ** time. Any timestamp that matches current time (to within 256 secondsf
558 ** would be in a CAF file that innd is actively
559 ** writing, in which case we would not want to cache the TOC for that
563 if (SMpreopen && amount == RETR_STAT) {
565 if (timestamp < ((now >> 8) & 0xffffff)) {
566 return StatArticle(timestamp, artnum, token.class);
570 path = MakePath(timestamp, token.class);
571 if ((art = OpenArticle(path, artnum, amount)) != (ARTHANDLE *)NULL) {
572 art->arrived = timestamp<<8; /* XXX not quite accurate arrival time,
573 ** but getting a more accurate one would
574 ** require more fiddling with CAF innards.
577 art->token = &ret_token;
583 void timecaf_freearticle(ARTHANDLE *article) {
584 PRIV_TIMECAF *private;
589 if (article->private) {
590 private = (PRIV_TIMECAF *)article->private;
591 if (innconf->articlemmap)
592 munmap(private->mmapbase, private->mmaplen);
594 free(private->artdata);
596 closedir(private->top);
598 closedir(private->sec);
600 closedir(private->ter);
602 free(private->curtoc);
608 /* Do cancels of all the article ids collected for a given pathname. */
612 if (DeletePath != NULL) {
613 if (NumDeleteArtnums != 0) {
615 ** Murgle. If we are trying to cancel something out of the
616 ** currently open-for-writing file, we need to close it before
617 ** doing CAFRemove...
619 if (WritingFile.path != NULL && strcmp(WritingFile.path, DeletePath) == 0) {
620 CloseOpenFile(&WritingFile);
622 /* XXX should really check err. code here, but not much we can really do. */
623 CAFRemoveMultArts(DeletePath, NumDeleteArtnums, DeleteArtnums);
625 DeleteArtnums = NULL;
626 NumDeleteArtnums = MaxDeleteArtnums = 0;
633 bool timecaf_cancel(TOKEN token) {
638 BreakToken(token, &now, &seqnum);
639 path = MakePath(now, token.class);
640 if (DeletePath == NULL) {
642 } else if (strcmp(DeletePath, path) != 0) {
643 /* different path, so flush all pending cancels. */
647 free(path); /* free redundant copy of path */
649 if (NumDeleteArtnums >= MaxDeleteArtnums) {
650 /* allocate/expand storage for artnums. */
651 if (MaxDeleteArtnums == 0) {
652 MaxDeleteArtnums = 100;
654 MaxDeleteArtnums *= 2;
656 DeleteArtnums = xrealloc(DeleteArtnums, MaxDeleteArtnums * sizeof(ARTNUM));
658 DeleteArtnums[NumDeleteArtnums++] = seqnum;
663 static struct dirent *FindDir(DIR *dir, FINDTYPE type) {
666 while ((de = readdir(dir)) != NULL) {
667 if (type == FIND_TOPDIR)
668 if ((strlen(de->d_name) == 10) &&
669 (strncmp(de->d_name, "timecaf-", 8) == 0) &&
670 CTYPE(isxdigit, de->d_name[8]) &&
671 CTYPE(isxdigit, de->d_name[9]))
674 if (type == FIND_DIR)
675 if ((strlen(de->d_name) == 2)
676 && CTYPE(isxdigit, de->d_name[0])
677 && CTYPE(isxdigit, de->d_name[1]))
680 if (type == FIND_CAF)
681 if ((strlen(de->d_name) == 7) &&
682 CTYPE(isxdigit, de->d_name[0]) &&
683 CTYPE(isxdigit, de->d_name[1]) &&
684 CTYPE(isxdigit, de->d_name[2]) &&
685 CTYPE(isxdigit, de->d_name[3]) &&
686 (de->d_name[4] == '.') &&
687 (de->d_name[5] == 'C') &&
688 (de->d_name[6] == 'F'))
695 /* Grovel thru a CAF table-of-contents finding the next still-existing article */
697 FindNextArt(const CAFHEADER *head, CAFTOCENT *toc, ARTNUM *artp)
703 art = head->Low - 1; /* we never use art # 0, so 0 is a flag to start
704 searching at the beginning */
708 if (art > head->High) return false; /* ran off the end of the TOC */
709 tocp = &toc[art - head->Low];
710 if (tocp->Size != 0) {
711 /* got a valid article */
720 ARTHANDLE *timecaf_next(const ARTHANDLE *article, const RETRTYPE amount) {
721 PRIV_TIMECAF priv, *newpriv;
726 length = strlen(innconf->patharticles) + 32;
727 path = xmalloc(length);
728 if (article == NULL) {
737 priv = *(PRIV_TIMECAF *)article->private;
738 free(article->private);
739 free((void *)article);
740 if (innconf->articlemmap)
741 munmap(priv.mmapbase, priv.mmaplen);
746 while (priv.curtoc == NULL || !FindNextArt(&priv.curheader, priv.curtoc, &priv.curartnum)) {
751 while (!priv.ter || ((priv.terde = FindDir(priv.ter, FIND_CAF)) == NULL)) {
756 while (!priv.sec || ((priv.secde = FindDir(priv.sec, FIND_DIR)) == NULL)) {
761 if (!priv.top || ((priv.topde = FindDir(priv.top, FIND_TOPDIR)) == NULL)) {
769 snprintf(path, length, "%s", innconf->patharticles);
770 if ((priv.top = opendir(path)) == NULL) {
771 SMseterror(SMERR_UNDEFINED, NULL);
775 if ((priv.topde = FindDir(priv.top, FIND_TOPDIR)) == NULL) {
776 SMseterror(SMERR_UNDEFINED, NULL);
782 snprintf(path, length, "%s/%s", innconf->patharticles, priv.topde->d_name);
783 if ((priv.sec = opendir(path)) == NULL)
786 snprintf(path, length, "%s/%s/%s", innconf->patharticles, priv.topde->d_name, priv.secde->d_name);
787 if ((priv.ter = opendir(path)) == NULL)
790 snprintf(path, length, "%s/%s/%s/%s", innconf->patharticles, priv.topde->d_name, priv.secde->d_name, priv.terde->d_name);
791 if ((priv.curtoc = CAFReadTOC(path, &priv.curheader)) == NULL)
795 snprintf(path, length, "%s/%s/%s/%s", innconf->patharticles, priv.topde->d_name, priv.secde->d_name, priv.terde->d_name);
796 art = OpenArticle(path, priv.curartnum, amount);
797 if (art == (ARTHANDLE *)NULL) {
798 art = xmalloc(sizeof(ARTHANDLE));
799 art->type = TOKEN_TIMECAF;
802 art->private = xmalloc(sizeof(PRIV_TIMECAF));
804 newpriv = (PRIV_TIMECAF *)art->private;
805 newpriv->top = priv.top;
806 newpriv->sec = priv.sec;
807 newpriv->ter = priv.ter;
808 newpriv->topde = priv.topde;
809 newpriv->secde = priv.secde;
810 newpriv->terde = priv.terde;
811 newpriv->curheader = priv.curheader;
812 newpriv->curtoc = priv.curtoc;
813 newpriv->curartnum = priv.curartnum;
815 snprintf(path, length, "%s/%s/%s", priv.topde->d_name, priv.secde->d_name, priv.terde->d_name);
816 art->token = PathNumToToken(path, priv.curartnum);
817 art->arrived = priv.curtoc[priv.curartnum - priv.curheader.Low].ModTime;
822 bool timecaf_ctl(PROBETYPE type, TOKEN *token UNUSED, void *value) {
823 struct artngnum *ann;
827 if ((ann = (struct artngnum *)value) == NULL)
829 /* make SMprobe() call timecaf_retrieve() */
837 bool timecaf_flushcacheddata(FLUSHTYPE type) {
838 if (type == SM_ALL || type == SM_CANCELEDART)
844 timecaf_printfiles(FILE *file, TOKEN token, char **xref UNUSED,
847 fprintf(file, "%s\n", TokenToText(token));
850 void timecaf_shutdown(void) {
851 CloseOpenFile(&WritingFile);