1 /*
2 **  Like the timehash storage method (and heavily inspired by it), but uses
3 **  the CAF library to store multiple articles in a single file.
4 */
5 
6 #include "portable/system.h"
7 
8 #include "portable/mmap.h"
9 #include <ctype.h>
10 #include <dirent.h>
11 #include <errno.h>
12 #include <fcntl.h>
13 #include <sys/stat.h>
14 #include <time.h>
15 
16 #include "caf.h"
17 #include "inn/fdflag.h"
18 #include "inn/innconf.h"
19 #include "inn/libinn.h"
20 #include "inn/messages.h"
21 #include "inn/paths.h"
22 #include "inn/wire.h"
23 #include "methods.h"
24 #include "timecaf.h"
25 
26 /* Needed for htonl() and friends on AIX 4.1. */
27 #include <netinet/in.h>
28 
29 typedef struct {
30     char *artdata;  /* start of the article data -- may be mmaped */
31     char *mmapbase; /* actual start of mmaped region (on pagesize bndry, not
32                        necessarily == artdaya */
33     unsigned int artlen;  /* art length. */
34     size_t mmaplen;       /* length of mmap region. */
35     DIR *top;             /* open handle on top level dir. */
36     DIR *sec;             /* open handle on the 2nd level directory */
37     DIR *ter;             /* open handle on 3rd level dir. */
38     struct dirent *topde; /* last entry we got from top */
39     struct dirent *secde; /* last entry we got from sec */
40     struct dirent *terde; /* last entry we got from sec */
41     CAFTOCENT *curtoc;
42     ARTNUM curartnum;
43     CAFHEADER curheader;
44 } PRIV_TIMECAF;
45 
46 /* current path/fd for an open CAF file */
47 typedef struct {
48     char *path; /* path to file. */
49     int fd;     /* open fd -- -1 if no file currently open. */
50 } CAFOPENFILE;
51 
52 static CAFOPENFILE ReadingFile, WritingFile;
53 static char *DeletePath;
54 static ARTNUM *DeleteArtnums;
55 static unsigned int NumDeleteArtnums, MaxDeleteArtnums;
56 
57 typedef enum
58 {
59     FIND_DIR,
60     FIND_CAF,
61     FIND_TOPDIR
62 } FINDTYPE;
63 
64 /*
65 ** Structures for the cache for stat information (to make expireover etc.)
66 ** faster.
67 **
68 ** The first structure contains the TOC info for a single CAF file.  The 2nd
69 ** one has pointers to the info for up to 256 CAF files, indexed
70 ** by the 2nd least significant byte of the arrival time.
71 */
72 
73 struct caftoccacheent {
74     CAFTOCENT *toc;
75     CAFHEADER header;
76 };
77 typedef struct caftoccacheent CAFTOCCACHEENT;
78 
79 struct caftocl1cache {
80     CAFTOCCACHEENT *entries[256];
81 };
82 typedef struct caftocl1cache CAFTOCL1CACHE;
83 
84 /*
85 ** and similar structures indexed by the 3rd and 4th bytes of the arrival time.
86 ** pointing to the lower level structures.  Note that the top level structure
87 ** (the one indexed by the MSByte of the timestamp) is likely to have only
88 ** one active pointer, unless your spool keeps more than 194 days of articles,
89 ** but it doesn't cost much to keep that one structure around and keep the
90 ** code general.
91 */
92 
93 struct caftocl2cache {
94     CAFTOCL1CACHE *l1ptr[256];
95 };
96 typedef struct caftocl2cache CAFTOCL2CACHE;
97 
98 struct caftocl3cache {
99     CAFTOCL2CACHE *l2ptr[256];
100 };
101 typedef struct caftocl3cache CAFTOCL3CACHE;
102 
103 static CAFTOCL3CACHE *TOCCache[256]; /* indexed by storage class! */
104 static int TOCCacheHits, TOCCacheMisses;
105 
106 /*
107 **  The token is @04nn00aabbccyyyyxxxx0000000000000000@
108 **  where "04" is the timecaf method number,
109 **  "nn" the hexadecimal value of the storage class,
110 **  "aabbccdd" the arrival time in hexadecimal (dd is unused),
111 **  "xxxxyyyy" the hexadecimal sequence number seqnum.
112 **
113 **  innconf->patharticles + '/timecaf-nn/bb/aacc.CF'
114 **  where "nn" is the hexadecimal value of the storage class,
115 **  "aabbccdd" the arrival time in hexadecimal (dd is unused).
116 */
117 char *
timecaf_explaintoken(const TOKEN token)118 timecaf_explaintoken(const TOKEN token)
119 {
120     char *text;
121     uint32_t arrival;
122     uint16_t seqnum1;
123     uint16_t seqnum2;
124 
125     memcpy(&arrival, &token.token[0], sizeof(arrival));
126     memcpy(&seqnum1, &token.token[4], sizeof(seqnum1));
127     memcpy(&seqnum2, &token.token[6], sizeof(seqnum2));
128 
129     xasprintf(&text,
130               "method=timecaf class=%u time=%lu seqnum=%lu "
131               "file=%s/timecaf-%02x/%02x/%02x%02x.CF",
132               (unsigned int) token.class,
133               ((unsigned long) (ntohl(arrival))) << 8,
134               ((unsigned long) ntohs(seqnum1))
135                   + (((unsigned long) ntohs(seqnum2)) << 16),
136               innconf->patharticles, token.class, (ntohl(arrival) >> 8) & 0xff,
137               (ntohl(arrival) >> 16) & 0xff, ntohl(arrival) & 0xff);
138 
139     return text;
140 }
141 
142 static TOKEN
MakeToken(time_t now,ARTNUM seqnum,STORAGECLASS class,TOKEN * oldtoken)143 MakeToken(time_t now, ARTNUM seqnum, STORAGECLASS class, TOKEN *oldtoken)
144 {
145     TOKEN token;
146     uint32_t i;
147     uint16_t s;
148 
149     if (oldtoken == (TOKEN *) NULL)
150         memset(&token, '\0', sizeof(token));
151     else
152         memcpy(&token, oldtoken, sizeof(token));
153     token.type = TOKEN_TIMECAF;
154     token.class = class;
155     i = htonl(now);
156     memcpy(token.token, &i, sizeof(i));
157     s = htons(seqnum & 0xffff);
158     memcpy(&token.token[sizeof(i)], &s, sizeof(s));
159     s = htons((seqnum >> 16) & 0xffff);
160     memcpy(&token.token[sizeof(i) + sizeof(s)], &s, sizeof(s));
161     return token;
162 }
163 
164 
165 static void
BreakToken(TOKEN token,time_t * now,ARTNUM * seqnum)166 BreakToken(TOKEN token, time_t *now, ARTNUM *seqnum)
167 {
168     uint32_t i;
169     uint16_t s1 = 0;
170     uint16_t s2 = 0;
171 
172     memcpy(&i, token.token, sizeof(i));
173     memcpy(&s1, &token.token[sizeof(i)], sizeof(s1));
174     memcpy(&s2, &token.token[sizeof(i) + sizeof(s1)], sizeof(s2));
175     *now = ntohl(i);
176     *seqnum = (ARTNUM)((ntohs(s2) << 16) + ntohs(s1));
177 }
178 
179 /*
180 ** Note: the time here is really "time>>8", i.e. a timestamp that's been
181 ** shifted right by 8 bits.
182 */
183 static char *
MakePath(time_t now,const STORAGECLASS class)184 MakePath(time_t now, const STORAGECLASS class)
185 {
186     char *path;
187     size_t length;
188 
189     length = strlen(innconf->patharticles) + 32;
190     path = xmalloc(length);
191     snprintf(path, length, "%s/timecaf-%02x/%02x/%02x%02x.CF",
192              innconf->patharticles, class, (unsigned int) ((now >> 8) & 0xff),
193              (unsigned int) ((now >> 16) & 0xff), (unsigned int) (now & 0xff));
194 
195     return path;
196 }
197 
198 static TOKEN *
PathNumToToken(char * path,ARTNUM artnum)199 PathNumToToken(char *path, ARTNUM artnum)
200 {
201     int n;
202     unsigned int tclass, t1, t2;
203     STORAGECLASS class;
204     time_t timestamp;
205     static TOKEN token;
206 
207     n = sscanf(path, "timecaf-%02x/%02x/%04x.CF", &tclass, &t1, &t2);
208     if (n != 3)
209         return (TOKEN *) NULL;
210     timestamp =
211         ((t1 << 8) & 0xff00) | ((t2 << 8) & 0xff0000) | ((t2 << 0) & 0xff);
212     class = tclass;
213     token = MakeToken(timestamp, artnum, class, (TOKEN *) NULL);
214     return &token;
215 }
216 
217 
218 bool
timecaf_init(SMATTRIBUTE * attr)219 timecaf_init(SMATTRIBUTE *attr)
220 {
221     if (attr == NULL) {
222         warn("timecaf: attr is NULL");
223         SMseterror(SMERR_INTERNAL, "attr is NULL");
224         return false;
225     }
226     attr->selfexpire = false;
227     attr->expensivestat = false;
228     if (STORAGE_TOKEN_LENGTH < 6) {
229         warn("timecaf: token length is less than six bytes");
230         SMseterror(SMERR_TOKENSHORT, NULL);
231         return false;
232     }
233     ReadingFile.fd = WritingFile.fd = -1;
234     ReadingFile.path = WritingFile.path = (char *) NULL;
235     return true;
236 }
237 
238 /*
239 ** Routines for managing the 'TOC cache' (cache of TOCs of various CAF files)
240 **
241 ** Attempt to look up a given TOC entry in the cache.  Takes the timestamp
242 ** as arguments.
243 */
244 
245 static CAFTOCCACHEENT *
CheckTOCCache(time_t timestamp,STORAGECLASS tokenclass)246 CheckTOCCache(time_t timestamp, STORAGECLASS tokenclass)
247 {
248     CAFTOCL2CACHE *l2;
249     CAFTOCL1CACHE *l1;
250     CAFTOCCACHEENT *cent;
251     unsigned char tmp;
252 
253     if (TOCCache[tokenclass] == NULL)
254         return NULL; /* cache is empty */
255 
256     tmp = (timestamp >> 16) & 0xff;
257     l2 = TOCCache[tokenclass]->l2ptr[tmp];
258     if (l2 == NULL)
259         return NULL;
260 
261     tmp = (timestamp >> 8) & 0xff;
262     l1 = l2->l1ptr[tmp];
263     if (l1 == NULL)
264         return NULL;
265 
266     tmp = (timestamp) &0xff;
267     cent = l1->entries[tmp];
268 
269     ++TOCCacheHits;
270     return cent;
271 }
272 
273 /*
274 ** Add given TOC and header to the cache.  Assume entry is not already in
275 ** cache.
276 */
277 static CAFTOCCACHEENT *
AddTOCCache(time_t timestamp,CAFTOCENT * toc,CAFHEADER head,STORAGECLASS tokenclass)278 AddTOCCache(time_t timestamp, CAFTOCENT *toc, CAFHEADER head,
279             STORAGECLASS tokenclass)
280 {
281     CAFTOCL2CACHE *l2;
282     CAFTOCL1CACHE *l1;
283     CAFTOCCACHEENT *cent;
284     unsigned char tmp;
285     int i;
286 
287     if (TOCCache[tokenclass] == NULL) {
288         TOCCache[tokenclass] = xmalloc(sizeof(CAFTOCL3CACHE));
289         for (i = 0; i < 256; ++i)
290             TOCCache[tokenclass]->l2ptr[i] = NULL;
291     }
292 
293     tmp = (timestamp >> 16) & 0xff;
294     l2 = TOCCache[tokenclass]->l2ptr[tmp];
295     if (l2 == NULL) {
296         TOCCache[tokenclass]->l2ptr[tmp] = l2 = xmalloc(sizeof(CAFTOCL2CACHE));
297         for (i = 0; i < 256; ++i)
298             l2->l1ptr[i] = NULL;
299     }
300 
301     tmp = (timestamp >> 8) & 0xff;
302     l1 = l2->l1ptr[tmp];
303     if (l1 == NULL) {
304         l2->l1ptr[tmp] = l1 = xmalloc(sizeof(CAFTOCL1CACHE));
305         for (i = 0; i < 256; ++i)
306             l1->entries[i] = NULL;
307     }
308 
309     tmp = (timestamp) &0xff;
310     cent = xmalloc(sizeof(CAFTOCCACHEENT));
311     l1->entries[tmp] = cent;
312 
313     cent->header = head;
314     cent->toc = toc;
315     ++TOCCacheMisses;
316     return cent;
317 }
318 
319 /*
320 ** Do stating of an article, going thru the TOC cache if possible.
321 */
322 
323 static ARTHANDLE *
StatArticle(time_t timestamp,ARTNUM artnum,STORAGECLASS tokenclass)324 StatArticle(time_t timestamp, ARTNUM artnum, STORAGECLASS tokenclass)
325 {
326     CAFTOCCACHEENT *cent;
327     CAFTOCENT *toc;
328     CAFHEADER head;
329     char *path;
330     CAFTOCENT *tocentry;
331     ARTHANDLE *art;
332 
333     cent = CheckTOCCache(timestamp, tokenclass);
334     if (cent == NULL) {
335         path = MakePath(timestamp, tokenclass);
336         toc = CAFReadTOC(path, &head);
337         if (toc == NULL) {
338             if (caf_error == CAF_ERR_ARTNOTHERE) {
339                 SMseterror(SMERR_NOENT, NULL);
340             } else {
341                 SMseterror(SMERR_UNDEFINED, NULL);
342             }
343             free(path);
344             return NULL;
345         }
346         cent = AddTOCCache(timestamp, toc, head, tokenclass);
347         free(path);
348     }
349 
350     /* check current TOC for the given artnum. */
351     if (artnum < cent->header.Low || artnum > cent->header.High) {
352         SMseterror(SMERR_NOENT, NULL);
353         return NULL;
354     }
355 
356     tocentry = &(cent->toc[artnum - cent->header.Low]);
357     if (tocentry->Size == 0) {
358         /* no article with that article number present */
359         SMseterror(SMERR_NOENT, NULL);
360         return NULL;
361     }
362 
363     /* stat is a success, so build a null art struct to represent that. */
364     art = xmalloc(sizeof(ARTHANDLE));
365     art->type = TOKEN_TIMECAF;
366     art->data = NULL;
367     art->len = 0;
368     art->private = NULL;
369     return art;
370 }
371 
372 
373 static void
CloseOpenFile(CAFOPENFILE * foo)374 CloseOpenFile(CAFOPENFILE *foo)
375 {
376     if (foo->fd >= 0) {
377         close(foo->fd);
378         foo->fd = -1;
379         free(foo->path);
380         foo->path = NULL;
381     }
382 }
383 
384 TOKEN
timecaf_store(const ARTHANDLE article,const STORAGECLASS class)385 timecaf_store(const ARTHANDLE article, const STORAGECLASS class)
386 {
387     char *path;
388     char *p;
389     time_t now;
390     time_t timestamp;
391     TOKEN token;
392     int fd;
393     ssize_t result;
394     ARTNUM art;
395 
396     if (article.arrived == (time_t) 0)
397         now = time(NULL);
398     else
399         now = article.arrived;
400 
401     timestamp = now >> 8;
402     art = 0; /* magic: 0=="next available article number. */
403 
404     memset(&token, 0, sizeof(token));
405 
406     path = MakePath(timestamp, class);
407     /* check to see if we have this CAF file already open. */
408     if (WritingFile.fd < 0 || strcmp(WritingFile.path, path) != 0) {
409         /* we're writing to a different file, close old one and start new one.
410          */
411         CloseOpenFile(&WritingFile);
412         fd = CAFOpenArtWrite(path, &art, true, article.len);
413         if (fd < 0) {
414             if (caf_error == CAF_ERR_IO && caf_errno == ENOENT) {
415                 /* directories in the path don't exist, try creating them. */
416                 p = strrchr(path, '/');
417                 *p = '\0';
418                 if (!MakeDirectory(path, true)) {
419                     syswarn("timecaf: could not create directory %s", path);
420                     token.type = TOKEN_EMPTY;
421                     free(path);
422                     SMseterror(SMERR_UNDEFINED, NULL);
423                     token.type = TOKEN_EMPTY;
424                     return token;
425                 } else {
426                     *p = '/';
427                     fd = CAFOpenArtWrite(path, &art, true, article.len);
428                     if (fd < 0) {
429                         warn("timecaf: could not OpenArtWrite %s/%ld: %s",
430                              path, art, CAFErrorStr());
431                         SMseterror(SMERR_UNDEFINED, NULL);
432                         free(path);
433                         token.type = TOKEN_EMPTY;
434                         return token;
435                     }
436                 }
437             } else {
438                 warn("timecaf: could not OpenArtWrite %s/%ld: %s", path, art,
439                      CAFErrorStr());
440                 SMseterror(SMERR_UNDEFINED, NULL);
441                 free(path);
442                 token.type = TOKEN_EMPTY;
443                 return token;
444             }
445         }
446     } else {
447         /* can reuse existing fd, assuming all goes well. */
448         fd = WritingFile.fd;
449 
450         /* nuke extraneous copy of path to avoid mem leaks. */
451         free(path);
452         path = WritingFile.path;
453 
454         if (CAFStartWriteFd(fd, &art, article.len) < 0) {
455             warn("timecaf: could not OpenArtWriteFd %s/%ld: %s", path, art,
456                  CAFErrorStr());
457             SMseterror(SMERR_UNDEFINED, NULL);
458             free(path);
459             token.type = TOKEN_EMPTY;
460             return token;
461         }
462     }
463     WritingFile.fd = fd;
464     WritingFile.path = path;
465     fdflag_close_exec(fd, true);
466     result = xwritev(fd, article.iov, article.iovcnt);
467     if (result != (ssize_t) article.len) {
468         SMseterror(SMERR_UNDEFINED, NULL);
469         syswarn("timecaf: error writing %s", path);
470         token.type = TOKEN_EMPTY;
471         CloseOpenFile(&WritingFile);
472         return token;
473     }
474     if (CAFFinishArtWrite(fd) < 0) {
475         SMseterror(SMERR_UNDEFINED, NULL);
476         warn("timecaf: error writing %s: %s", path, CAFErrorStr());
477         token.type = TOKEN_EMPTY;
478         CloseOpenFile(&WritingFile);
479         return token;
480     }
481 
482     return MakeToken(timestamp, art, class, article.token);
483 }
484 
485 /* Get a handle to article artnum in CAF-file path. */
486 static ARTHANDLE *
OpenArticle(const char * path,ARTNUM artnum,const RETRTYPE amount)487 OpenArticle(const char *path, ARTNUM artnum, const RETRTYPE amount)
488 {
489     int fd;
490     PRIV_TIMECAF *private;
491     char *p;
492     size_t len;
493     ARTHANDLE *art;
494     static long pagesize = 0;
495 
496     if (pagesize == 0) {
497         pagesize = getpagesize();
498         if (pagesize < 0) {
499             syswarn("timecaf: getpagesize failed");
500             pagesize = 0;
501             return NULL;
502         }
503     }
504 
505     /* XXX need to figure some way to cache open fds or something? */
506     if ((fd = CAFOpenArtRead(path, artnum, &len)) < 0) {
507         if (caf_error == CAF_ERR_ARTNOTHERE) {
508             SMseterror(SMERR_NOENT, NULL);
509         } else {
510             SMseterror(SMERR_UNDEFINED, NULL);
511         }
512         return NULL;
513     }
514 
515     art = xmalloc(sizeof(ARTHANDLE));
516     art->type = TOKEN_TIMECAF;
517 
518     if (amount == RETR_STAT) {
519         art->data = NULL;
520         art->len = 0;
521         art->private = NULL;
522         close(fd);
523         return art;
524     }
525 
526   private
527     = xmalloc(sizeof(PRIV_TIMECAF));
528     art->private = (void *) private;
529   private
530     ->artlen = len;
531     if (innconf->articlemmap) {
532         off_t curoff, tmpoff;
533         size_t delta;
534 
535         curoff = lseek(fd, (off_t) 0, SEEK_CUR);
536         delta = curoff % pagesize;
537         tmpoff = curoff - delta;
538       private
539         ->mmaplen = len + delta;
540         if ((private->mmapbase = mmap(NULL, private->mmaplen, PROT_READ,
541                                       MAP_SHARED, fd, tmpoff))
542             == MAP_FAILED) {
543             SMseterror(SMERR_UNDEFINED, NULL);
544             syswarn("timecaf: could not mmap article");
545             free(art->private);
546             free(art);
547             return NULL;
548         }
549         mmap_invalidate(private->mmapbase, private->mmaplen);
550         if (amount == RETR_ALL)
551             madvise(private->mmapbase, private->mmaplen, MADV_WILLNEED);
552         else
553             madvise(private->mmapbase, private->mmaplen, MADV_SEQUENTIAL);
554       private
555         ->artdata = private->mmapbase + delta;
556     } else {
557       private
558         ->artdata = xmalloc(private->artlen);
559         if (read(fd, private->artdata, private->artlen) < 0) {
560             SMseterror(SMERR_UNDEFINED, NULL);
561             syswarn("timecaf: could not read article");
562             free(private->artdata);
563             free(art->private);
564             free(art);
565             return NULL;
566         }
567     }
568     close(fd);
569 
570   private
571     ->top = NULL;
572   private
573     ->sec = NULL;
574   private
575     ->ter = NULL;
576   private
577     ->curtoc = NULL;
578   private
579     ->curartnum = 0;
580   private
581     ->topde = NULL;
582   private
583     ->secde = NULL;
584   private
585     ->terde = NULL;
586 
587     if (amount == RETR_ALL) {
588         art->data = private->artdata;
589         art->len = private->artlen;
590         return art;
591     }
592 
593     if ((p = wire_findbody(private->artdata, private->artlen)) == NULL) {
594         SMseterror(SMERR_NOBODY, NULL);
595         if (innconf->articlemmap)
596             munmap(private->mmapbase, private->mmaplen);
597         else
598             free(private->artdata);
599         free(art->private);
600         free(art);
601         return NULL;
602     }
603 
604     if (amount == RETR_HEAD) {
605         art->data = private->artdata;
606         art->len = p - private->artdata;
607         /* Headers end just before the first empty line (\r\n). */
608         art->len = art->len - 2;
609         return art;
610     }
611 
612     if (amount == RETR_BODY) {
613         art->data = p + 4;
614         art->len = art->len - (private->artdata - p - 4);
615         return art;
616     }
617     SMseterror(SMERR_UNDEFINED, "Invalid retrieve request");
618     if (innconf->articlemmap)
619         munmap(private->mmapbase, private->mmaplen);
620     else
621         free(private->artdata);
622     free(art->private);
623     free(art);
624     return NULL;
625 }
626 
627 ARTHANDLE *
timecaf_retrieve(const TOKEN token,const RETRTYPE amount)628 timecaf_retrieve(const TOKEN token, const RETRTYPE amount)
629 {
630     time_t timestamp;
631     ARTNUM artnum;
632     char *path;
633     ARTHANDLE *art;
634     static TOKEN ret_token;
635     time_t now;
636 
637     if (token.type != TOKEN_TIMECAF) {
638         SMseterror(SMERR_INTERNAL, NULL);
639         return NULL;
640     }
641 
642     BreakToken(token, &timestamp, &artnum);
643 
644     /*
645     ** Do a possible shortcut on RETR_STAT requests, going through the "TOC
646     ** cache" we mentioned above.  We only try to go through the TOC Cache
647     ** under these conditions:
648     **   1) SMpreopen is true (so we're "preopening" the TOCs).
649     **   2) the timestamp is older than the timestamp corresponding to current
650     ** time.  Any timestamp that matches current time (to within 256 seconds)
651     ** would be in a CAF file that innd is actively
652     ** writing, in which case we would not want to cache the TOC for that
653     ** CAF file.
654     */
655 
656     if (SMpreopen && amount == RETR_STAT) {
657         now = time(NULL);
658         if (timestamp < ((now >> 8) & 0xffffff)) {
659             return StatArticle(timestamp, artnum, token.class);
660         }
661     }
662 
663     path = MakePath(timestamp, token.class);
664     if ((art = OpenArticle(path, artnum, amount)) != (ARTHANDLE *) NULL) {
665         art->arrived = timestamp
666                        << 8; /* XXX not quite accurate arrival time,
667                              ** but getting a more accurate one would
668                              ** require more fiddling with CAF innards.
669                              */
670         ret_token = token;
671         art->token = &ret_token;
672     }
673     free(path);
674     return art;
675 }
676 
677 void
timecaf_freearticle(ARTHANDLE * article)678 timecaf_freearticle(ARTHANDLE *article)
679 {
680     PRIV_TIMECAF *private;
681 
682     if (!article)
683         return;
684 
685     if (article->private) {
686       private
687         = (PRIV_TIMECAF *) article->private;
688         if (innconf->articlemmap)
689             munmap(private->mmapbase, private->mmaplen);
690         else
691             free(private->artdata);
692         if (private->top)
693             closedir(private->top);
694         if (private->sec)
695             closedir(private->sec);
696         if (private->ter)
697             closedir(private->ter);
698         if (private->curtoc)
699             free(private->curtoc);
700         free(private);
701     }
702     free(article);
703 }
704 
705 /* Do cancels of all the article ids collected for a given pathname. */
706 
707 static void
DoCancels(void)708 DoCancels(void)
709 {
710     if (DeletePath != NULL) {
711         if (NumDeleteArtnums != 0) {
712             /*
713             ** Murgle. If we are trying to cancel something out of the
714             ** currently open-for-writing file, we need to close it before
715             ** doing CAFRemove...
716             */
717             if (WritingFile.path != NULL
718                 && strcmp(WritingFile.path, DeletePath) == 0) {
719                 CloseOpenFile(&WritingFile);
720             }
721             /* XXX should really check err. code here, but not much we can
722              * really do. */
723             CAFRemoveMultArts(DeletePath, NumDeleteArtnums, DeleteArtnums);
724             free(DeleteArtnums);
725             DeleteArtnums = NULL;
726             NumDeleteArtnums = MaxDeleteArtnums = 0;
727         }
728         free(DeletePath);
729         DeletePath = NULL;
730     }
731 }
732 
733 bool
timecaf_cancel(TOKEN token)734 timecaf_cancel(TOKEN token)
735 {
736     time_t now;
737     ARTNUM seqnum;
738     char *path;
739 
740     BreakToken(token, &now, &seqnum);
741     path = MakePath(now, token.class);
742     if (DeletePath == NULL) {
743         DeletePath = path;
744     } else if (strcmp(DeletePath, path) != 0) {
745         /* different path, so flush all pending cancels. */
746         DoCancels();
747         DeletePath = path;
748     } else {
749         free(path); /* free redundant copy of path */
750     }
751     if (NumDeleteArtnums >= MaxDeleteArtnums) {
752         /* allocate/expand storage for artnums. */
753         if (MaxDeleteArtnums == 0) {
754             MaxDeleteArtnums = 100;
755         } else {
756             MaxDeleteArtnums *= 2;
757         }
758         DeleteArtnums =
759             xrealloc(DeleteArtnums, MaxDeleteArtnums * sizeof(ARTNUM));
760     }
761     DeleteArtnums[NumDeleteArtnums++] = seqnum;
762 
763     return true;
764 }
765 
766 static struct dirent *
FindDir(DIR * dir,FINDTYPE type)767 FindDir(DIR *dir, FINDTYPE type)
768 {
769     struct dirent *de;
770 
771     while ((de = readdir(dir)) != NULL) {
772         if (type == FIND_TOPDIR)
773             if ((strlen(de->d_name) == 10)
774                 && (strncmp(de->d_name, "timecaf-", 8) == 0)
775                 && isxdigit((unsigned char) de->d_name[8])
776                 && isxdigit((unsigned char) de->d_name[9]))
777                 return de;
778 
779         if (type == FIND_DIR)
780             if ((strlen(de->d_name) == 2)
781                 && isxdigit((unsigned char) de->d_name[0])
782                 && isxdigit((unsigned char) de->d_name[1]))
783                 return de;
784 
785         if (type == FIND_CAF)
786             if ((strlen(de->d_name) == 7)
787                 && isxdigit((unsigned char) de->d_name[0])
788                 && isxdigit((unsigned char) de->d_name[1])
789                 && isxdigit((unsigned char) de->d_name[2])
790                 && isxdigit((unsigned char) de->d_name[3])
791                 && (de->d_name[4] == '.') && (de->d_name[5] == 'C')
792                 && (de->d_name[6] == 'F'))
793                 return de;
794     }
795 
796     return NULL;
797 }
798 
799 /* Grovel thru a CAF table-of-contents finding the next still-existing article
800  */
801 static int
FindNextArt(const CAFHEADER * head,CAFTOCENT * toc,ARTNUM * artp)802 FindNextArt(const CAFHEADER *head, CAFTOCENT *toc, ARTNUM *artp)
803 {
804     ARTNUM art;
805     CAFTOCENT *tocp;
806     art = *artp;
807     if (art == 0) {
808         art = head->Low - 1; /* we never use art # 0, so 0 is a flag to start
809                                searching at the beginning */
810     }
811     while (true) {
812         art++;
813         if (art > head->High)
814             return false; /* ran off the end of the TOC */
815         tocp = &toc[art - head->Low];
816         if (tocp->Size != 0) {
817             /* got a valid article */
818             *artp = art;
819             return true;
820         }
821     }
822 }
823 
824 
825 ARTHANDLE *
timecaf_next(ARTHANDLE * article,const RETRTYPE amount)826 timecaf_next(ARTHANDLE *article, const RETRTYPE amount)
827 {
828     PRIV_TIMECAF priv, *newpriv;
829     char *path;
830     ARTHANDLE *art;
831     size_t length;
832 
833     length = strlen(innconf->patharticles) + 32;
834     path = xmalloc(length);
835     if (article == NULL) {
836         priv.top = NULL;
837         priv.sec = NULL;
838         priv.ter = NULL;
839         priv.curtoc = NULL;
840         priv.topde = NULL;
841         priv.secde = NULL;
842         priv.terde = NULL;
843     } else {
844         priv = *(PRIV_TIMECAF *) article->private;
845         free(article->private);
846         free(article);
847         if (innconf->articlemmap)
848             munmap(priv.mmapbase, priv.mmaplen);
849         else
850             free(priv.artdata);
851     }
852 
853     while (priv.curtoc == NULL
854            || !FindNextArt(&priv.curheader, priv.curtoc, &priv.curartnum)) {
855         if (priv.curtoc) {
856             free(priv.curtoc);
857             priv.curtoc = NULL;
858         }
859         while (!priv.ter
860                || ((priv.terde = FindDir(priv.ter, FIND_CAF)) == NULL)) {
861             if (priv.ter) {
862                 closedir(priv.ter);
863                 priv.ter = NULL;
864             }
865             while (!priv.sec
866                    || ((priv.secde = FindDir(priv.sec, FIND_DIR)) == NULL)) {
867                 if (priv.sec) {
868                     closedir(priv.sec);
869                     priv.sec = NULL;
870                 }
871                 if (!priv.top
872                     || ((priv.topde = FindDir(priv.top, FIND_TOPDIR))
873                         == NULL)) {
874                     if (priv.top) {
875                         /* end of search */
876                         closedir(priv.top);
877                         priv.top = NULL;
878                         free(path);
879                         return NULL;
880                     }
881                     snprintf(path, length, "%s", innconf->patharticles);
882                     if ((priv.top = opendir(path)) == NULL) {
883                         SMseterror(SMERR_UNDEFINED, NULL);
884                         free(path);
885                         return NULL;
886                     }
887                     if ((priv.topde = FindDir(priv.top, FIND_TOPDIR))
888                         == NULL) {
889                         SMseterror(SMERR_UNDEFINED, NULL);
890                         closedir(priv.top);
891                         free(path);
892                         return NULL;
893                     }
894                 }
895                 snprintf(path, length, "%s/%s", innconf->patharticles,
896                          priv.topde->d_name);
897                 if ((priv.sec = opendir(path)) == NULL)
898                     continue;
899             }
900             snprintf(path, length, "%s/%s/%s", innconf->patharticles,
901                      priv.topde->d_name, priv.secde->d_name);
902             if ((priv.ter = opendir(path)) == NULL)
903                 continue;
904         }
905         snprintf(path, length, "%s/%s/%s/%s", innconf->patharticles,
906                  priv.topde->d_name, priv.secde->d_name, priv.terde->d_name);
907         if ((priv.curtoc = CAFReadTOC(path, &priv.curheader)) == NULL)
908             continue;
909         priv.curartnum = 0;
910     }
911     snprintf(path, length, "%s/%s/%s/%s", innconf->patharticles,
912              priv.topde->d_name, priv.secde->d_name, priv.terde->d_name);
913     art = OpenArticle(path, priv.curartnum, amount);
914     if (art == (ARTHANDLE *) NULL) {
915         art = xmalloc(sizeof(ARTHANDLE));
916         art->type = TOKEN_TIMECAF;
917         art->data = NULL;
918         art->len = 0;
919         art->private = xmalloc(sizeof(PRIV_TIMECAF));
920     }
921     newpriv = (PRIV_TIMECAF *) art->private;
922     newpriv->top = priv.top;
923     newpriv->sec = priv.sec;
924     newpriv->ter = priv.ter;
925     newpriv->topde = priv.topde;
926     newpriv->secde = priv.secde;
927     newpriv->terde = priv.terde;
928     newpriv->curheader = priv.curheader;
929     newpriv->curtoc = priv.curtoc;
930     newpriv->curartnum = priv.curartnum;
931 
932     snprintf(path, length, "%s/%s/%s", priv.topde->d_name, priv.secde->d_name,
933              priv.terde->d_name);
934     art->token = PathNumToToken(path, priv.curartnum);
935     art->arrived = priv.curtoc[priv.curartnum - priv.curheader.Low].ModTime;
936     free(path);
937     return art;
938 }
939 
940 bool
timecaf_ctl(PROBETYPE type,TOKEN * token UNUSED,void * value)941 timecaf_ctl(PROBETYPE type, TOKEN *token UNUSED, void *value)
942 {
943     struct artngnum *ann;
944 
945     switch (type) {
946     case SMARTNGNUM:
947         if ((ann = (struct artngnum *) value) == NULL)
948             return false;
949         /* make SMprobe() call timecaf_retrieve() */
950         ann->artnum = 0;
951         return true;
952     default:
953         return false;
954     }
955 }
956 
957 bool
timecaf_flushcacheddata(FLUSHTYPE type)958 timecaf_flushcacheddata(FLUSHTYPE type)
959 {
960     if (type == SM_ALL || type == SM_CANCELLEDART)
961         DoCancels();
962     return true;
963 }
964 
965 void
timecaf_printfiles(FILE * file,TOKEN token,char ** xref UNUSED,int ngroups UNUSED)966 timecaf_printfiles(FILE *file, TOKEN token, char **xref UNUSED,
967                    int ngroups UNUSED)
968 {
969     fprintf(file, "%s\n", TokenToText(token));
970 }
971 
972 void
timecaf_shutdown(void)973 timecaf_shutdown(void)
974 {
975     CloseOpenFile(&WritingFile);
976     DoCancels();
977 }
978