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, ×tamp, &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