chiark / gitweb /
Rerun autoconf2.13 on etch and consequently refresh configure-hostname patch
[inn-innduct.git] / storage / timehash / timehash.c
1 /*  $Id: timehash.c 7412 2005-10-09 03:44:35Z eagle $
2 **
3 **  Timehash based storage method.
4 */
5
6 #include "config.h"
7 #include "clibrary.h"
8 #include "portable/mmap.h"
9 #include <ctype.h>
10 #include <dirent.h>
11 #include <errno.h>
12 #include <fcntl.h>
13 #include <netinet/in.h>
14 #include <syslog.h>
15 #include <sys/stat.h>
16 #include <time.h>
17
18 #include "inn/innconf.h"
19 #include "inn/wire.h"
20 #include "libinn.h"
21 #include "methods.h"
22 #include "paths.h"
23 #include "timehash.h"
24
25 typedef struct {
26     char                *base;    /* Base of the mmaped file */
27     int                 len;      /* Length of the file */
28     DIR                 *top;     /* Open handle on the top level directory */
29     DIR                 *sec;     /* Open handle on the 2nd level directory */
30     DIR                 *ter;     /* Open handle on the third level directory */
31     DIR                 *artdir;  /* Open handle on the article directory */
32     struct dirent       *topde;   /* dirent entry for the last entry retrieved in top */
33     struct dirent       *secde;   /* dirent entry for the last entry retrieved in sec */
34     struct dirent       *terde;   /* dirent entry for the last entry retrieved in ter */
35 } PRIV_TIMEHASH;
36
37 typedef enum {FIND_DIR, FIND_ART, FIND_TOPDIR} FINDTYPE;
38
39 static int SeqNum = 0;
40
41 static TOKEN MakeToken(time_t now, int seqnum, STORAGECLASS class, TOKEN *oldtoken) {
42     TOKEN               token;
43     unsigned int        i;
44     unsigned short      s;
45
46     if (oldtoken == (TOKEN *)NULL)
47         memset(&token, '\0', sizeof(token));
48     else 
49         memcpy(&token, oldtoken, sizeof(token));
50     token.type = TOKEN_TIMEHASH;
51     token.class = class;
52     i = htonl(now);
53     memcpy(token.token, &i, sizeof(i));
54     if (sizeof(i) > 4)
55         memmove(token.token, &token.token[sizeof(i) - 4], 4);
56     s = htons(seqnum);
57     memcpy(&token.token[4], &s + (sizeof(s) - 2), 2);
58     return token;
59 }
60
61 static void BreakToken(TOKEN token, int *now, int *seqnum) {
62     unsigned int        i;
63     unsigned short      s = 0;
64
65     memcpy(&i, token.token, sizeof(i));
66     memcpy(&s, &token.token[4], sizeof(s));
67     *now = ntohl(i);
68     *seqnum = (int)ntohs(s);
69 }
70
71 static char *MakePath(int now, int seqnum, const STORAGECLASS class) {
72     char *path;
73     size_t length;
74     
75     /* innconf->patharticles + '/time-zz/xx/xx/yyyy-xxxx' */
76     length = strlen(innconf->patharticles) + 32;
77     path = xmalloc(length);
78     snprintf(path, length, "%s/time-%02x/%02x/%02x/%04x-%04x",
79              innconf->patharticles, class,
80              (now >> 16) & 0xff, (now >> 8) & 0xff, seqnum,
81              (now & 0xff) | ((now >> 16 & 0xff00)));
82     return path;
83 }
84
85 static TOKEN *PathToToken(char *path) {
86     int                 n;
87     unsigned int        t1, t2, t3, seqnum, class;
88     time_t              now;
89     static TOKEN        token;
90
91     n = sscanf(path, "time-%02x/%02x/%02x/%04x-%04x", &class, &t1, &t2, &seqnum, &t3);
92     if (n != 5)
93         return (TOKEN *)NULL;
94     now = ((t1 << 16) & 0xff0000) | ((t2 << 8) & 0xff00) | ((t3 << 16) & 0xff000000) | (t3 & 0xff);
95     token = MakeToken(now, seqnum, class, (TOKEN *)NULL);
96     return &token;
97 }
98
99 bool timehash_init(SMATTRIBUTE *attr) {
100     if (attr == NULL) {
101         syslog(L_ERROR, "timehash: attr is NULL");
102         SMseterror(SMERR_INTERNAL, "attr is NULL");
103         return false;
104     }
105     attr->selfexpire = false;
106     attr->expensivestat = true;
107     if (STORAGE_TOKEN_LENGTH < 6) {
108         syslog(L_FATAL, "timehash: token length is less than 6 bytes");
109         SMseterror(SMERR_TOKENSHORT, NULL);
110         return false;
111     }
112     return true;
113 }
114
115 TOKEN timehash_store(const ARTHANDLE article, const STORAGECLASS class) {
116     char                *path;
117     char                *p;
118     time_t              now;
119     TOKEN               token;
120     int                 fd;
121     ssize_t             result;
122     int                 seq;
123     int                 i;
124
125     if (article.arrived == (time_t)0)
126         now = time(NULL);
127     else
128         now = article.arrived;
129
130     for (i = 0; i < 0x10000; i++) {
131         seq = SeqNum;
132         SeqNum = (SeqNum + 1) & 0xffff;
133         path = MakePath(now, seq, class);
134
135         if ((fd = open(path, O_CREAT|O_EXCL|O_WRONLY, ARTFILE_MODE)) < 0) {
136             if (errno == EEXIST)
137                 continue;
138             p = strrchr(path, '/');
139             *p = '\0';
140             if (!MakeDirectory(path, true)) {
141                 syslog(L_ERROR, "timehash: could not make directory %s %m", path);
142                 token.type = TOKEN_EMPTY;
143                 free(path);
144                 SMseterror(SMERR_UNDEFINED, NULL);
145                 return token;
146             } else {
147                 *p = '/';
148                 if ((fd = open(path, O_CREAT|O_EXCL|O_WRONLY, ARTFILE_MODE)) < 0) {
149                     SMseterror(SMERR_UNDEFINED, NULL);
150                     syslog(L_ERROR, "timehash: could not open %s %m", path);
151                     token.type = TOKEN_EMPTY;
152                     free(path);
153                     return token;
154                 }
155             }
156         }
157         break;
158     }
159     if (i == 0x10000) {
160         SMseterror(SMERR_UNDEFINED, NULL);
161         syslog(L_ERROR, "timehash: all sequence numbers for the time and class are reserved %lu %d", (unsigned long)now, class);
162         token.type = TOKEN_EMPTY;
163         free(path);
164         return token;
165     }
166
167     result = xwritev(fd, article.iov, article.iovcnt);
168     if (result != (ssize_t) article.len) {
169         SMseterror(SMERR_UNDEFINED, NULL);
170         syslog(L_ERROR, "timehash error writing %s %m", path);
171         close(fd);
172         token.type = TOKEN_EMPTY;
173         unlink(path);
174         free(path);
175         return token;
176     }
177     close(fd);
178     free(path);
179     return MakeToken(now, seq, class, article.token);
180 }
181
182 static ARTHANDLE *OpenArticle(const char *path, RETRTYPE amount) {
183     int                 fd;
184     PRIV_TIMEHASH       *private;
185     char                *p;
186     struct stat         sb;
187     ARTHANDLE           *art;
188
189     if (amount == RETR_STAT) {
190         if (access(path, R_OK) < 0) {
191             SMseterror(SMERR_UNDEFINED, NULL);
192             return NULL;
193         }
194         art = xmalloc(sizeof(ARTHANDLE));
195         art->type = TOKEN_TIMEHASH;
196         art->data = NULL;
197         art->len = 0;
198         art->private = NULL;
199         return art;
200     }
201
202     if ((fd = open(path, O_RDONLY)) < 0) {
203         SMseterror(SMERR_UNDEFINED, NULL);
204         return NULL;
205     }
206
207     art = xmalloc(sizeof(ARTHANDLE));
208     art->type = TOKEN_TIMEHASH;
209
210     if (fstat(fd, &sb) < 0) {
211         SMseterror(SMERR_UNDEFINED, NULL);
212         syslog(L_ERROR, "timehash: could not fstat article: %m");
213         free(art);
214         return NULL;
215     }
216     
217     private = xmalloc(sizeof(PRIV_TIMEHASH));
218     art->private = (void *)private;
219     private->len = sb.st_size;
220     if (innconf->articlemmap) {
221         if ((private->base = mmap(NULL, sb.st_size, PROT_READ, MAP_SHARED, fd, 0)) == MAP_FAILED) {
222             SMseterror(SMERR_UNDEFINED, NULL);
223             syslog(L_ERROR, "timehash: could not mmap article: %m");
224             free(art->private);
225             free(art);
226             return NULL;
227         }
228         if (amount == RETR_ALL)
229             madvise(private->base, sb.st_size, MADV_WILLNEED);
230         else
231             madvise(private->base, sb.st_size, MADV_SEQUENTIAL);
232     } else {
233         private->base = xmalloc(private->len);
234         if (read(fd, private->base, private->len) < 0) {
235             SMseterror(SMERR_UNDEFINED, NULL);
236             syslog(L_ERROR, "timehash: could not read article: %m");
237             free(private->base);
238             free(art->private);
239             free(art);
240             return NULL;
241         }
242     }
243     close(fd);
244
245     private->top = NULL;
246     private->sec = NULL;
247     private->ter = NULL;
248     private->artdir = NULL;
249     private->topde = NULL;
250     private->secde = NULL;
251     private->terde = NULL;
252     
253     if (amount == RETR_ALL) {
254         art->data = private->base;
255         art->len = private->len;
256         return art;
257     }
258     
259     if ((p = wire_findbody(private->base, private->len)) == NULL) {
260         SMseterror(SMERR_NOBODY, NULL);
261         if (innconf->articlemmap)
262             munmap(private->base, private->len);
263         else
264             free(private->base);
265         free(art->private);
266         free(art);
267         return NULL;
268     }
269
270     if (amount == RETR_HEAD) {
271         art->data = private->base;
272         art->len = p - private->base;
273         return art;
274     }
275
276     if (amount == RETR_BODY) {
277         art->data = p;
278         art->len = private->len - (p - private->base);
279         return art;
280     }
281     SMseterror(SMERR_UNDEFINED, "Invalid retrieve request");
282     if (innconf->articlemmap)
283         munmap(private->base, private->len);
284     else
285         free(private->base);
286     free(art->private);
287     free(art);
288     return NULL;
289 }
290
291 ARTHANDLE *timehash_retrieve(const TOKEN token, const RETRTYPE amount) {
292     int                 now;
293     int                 seqnum;
294     char                *path;
295     ARTHANDLE           *art;
296     static TOKEN        ret_token;
297     
298     if (token.type != TOKEN_TIMEHASH) {
299         SMseterror(SMERR_INTERNAL, NULL);
300         return NULL;
301     }
302
303     BreakToken(token, &now, &seqnum);
304     path = MakePath(now, seqnum, token.class);
305     if ((art = OpenArticle(path, amount)) != (ARTHANDLE *)NULL) {
306         art->arrived = now;
307         ret_token = token;
308         art->token = &ret_token;
309     }
310     free(path);
311     return art;
312 }
313
314 void timehash_freearticle(ARTHANDLE *article) {
315     PRIV_TIMEHASH       *private;
316
317     if (!article)
318         return;
319     
320     if (article->private) {
321         private = (PRIV_TIMEHASH *)article->private;
322         if (innconf->articlemmap)
323             munmap(private->base, private->len);
324         else
325             free(private->base);
326         if (private->top)
327             closedir(private->top);
328         if (private->sec)
329             closedir(private->sec);
330         if (private->ter)
331             closedir(private->ter);
332         if (private->artdir)
333             closedir(private->artdir);
334         free(private);
335     }
336     free(article);
337 }
338
339 bool timehash_cancel(TOKEN token) {
340     int                 now;
341     int                 seqnum;
342     char                *path;
343     int                 result;
344
345     BreakToken(token, &now, &seqnum);
346     path = MakePath(now, seqnum, token.class);
347     result = unlink(path);
348     free(path);
349     if (result < 0) {
350         SMseterror(SMERR_UNDEFINED, NULL);
351         return false;
352     }
353     return true;
354 }
355
356 static struct dirent *FindDir(DIR *dir, FINDTYPE type) {
357     struct dirent       *de;
358     
359     while ((de = readdir(dir)) != NULL) {
360         if (type == FIND_TOPDIR)
361             if ((strlen(de->d_name) == 7) &&
362                 (strncmp(de->d_name, "time-", 5) == 0) &&
363                 isxdigit((int)de->d_name[5]) &&
364                 isxdigit((int)de->d_name[6]))
365                 return de;
366
367         if (type == FIND_DIR)
368             if ((strlen(de->d_name) == 2) && isxdigit((int)de->d_name[0]) && isxdigit((int)de->d_name[1]))
369                 return de;
370
371         if (type == FIND_ART)
372             if ((strlen(de->d_name) == 9) &&
373                 isxdigit((int)de->d_name[0]) &&
374                 isxdigit((int)de->d_name[1]) &&
375                 isxdigit((int)de->d_name[2]) &&
376                 isxdigit((int)de->d_name[3]) &&
377                 isxdigit((int)de->d_name[5]) &&
378                 isxdigit((int)de->d_name[6]) &&
379                 isxdigit((int)de->d_name[7]) &&
380                 isxdigit((int)de->d_name[8]) &&
381                 (de->d_name[4] == '-'))
382                 return de;
383         }
384
385     return NULL;
386 }
387
388 ARTHANDLE *timehash_next(const ARTHANDLE *article, const RETRTYPE amount) {
389     PRIV_TIMEHASH       priv;
390     PRIV_TIMEHASH       *newpriv;
391     char                *path;
392     struct dirent       *de;
393     ARTHANDLE           *art;
394     int                 seqnum;
395     size_t              length;
396
397     length = strlen(innconf->patharticles) + 32;
398     path = xmalloc(length);
399     if (article == NULL) {
400         priv.top = NULL;
401         priv.sec = NULL;
402         priv.ter = NULL;
403         priv.artdir = NULL;
404         priv.topde = NULL;
405         priv.secde = NULL;
406         priv.terde = NULL;
407     } else {
408         priv = *(PRIV_TIMEHASH *)article->private;
409         free(article->private);
410         free((void *)article);
411         if (priv.base != NULL) {
412             if (innconf->articlemmap)
413                 munmap(priv.base, priv.len);
414             else
415                 free(priv.base);
416         }
417     }
418
419     while (!priv.artdir || ((de = FindDir(priv.artdir, FIND_ART)) == NULL)) {
420         if (priv.artdir) {
421             closedir(priv.artdir);
422             priv.artdir = NULL;
423         }
424         while (!priv.ter || ((priv.terde = FindDir(priv.ter, FIND_DIR)) == NULL)) {
425             if (priv.ter) {
426                 closedir(priv.ter);
427                 priv.ter = NULL;
428             }
429             while (!priv.sec || ((priv.secde = FindDir(priv.sec, FIND_DIR)) == NULL)) {
430                 if (priv.sec) {
431                     closedir(priv.sec);
432                     priv.sec = NULL;
433                 }
434                 if (!priv.top || ((priv.topde = FindDir(priv.top, FIND_TOPDIR)) == NULL)) {
435                     if (priv.top) {
436                         /* end of search */
437                         closedir(priv.top);
438                         priv.top = NULL;
439                         free(path);
440                         return NULL;
441                     }
442                     snprintf(path, length, "%s", innconf->patharticles);
443                     if ((priv.top = opendir(path)) == NULL) {
444                         SMseterror(SMERR_UNDEFINED, NULL);
445                         free(path);
446                         return NULL;
447                     }
448                     if ((priv.topde = FindDir(priv.top, FIND_TOPDIR)) == NULL) {
449                         SMseterror(SMERR_UNDEFINED, NULL);
450                         closedir(priv.top);
451                         free(path);
452                         return NULL;
453                     }
454                 }
455                 snprintf(path, length, "%s/%s", innconf->patharticles, priv.topde->d_name);
456                 if ((priv.sec = opendir(path)) == NULL)
457                     continue;
458             }
459             snprintf(path, length, "%s/%s/%s", innconf->patharticles, priv.topde->d_name, priv.secde->d_name);
460             if ((priv.ter = opendir(path)) == NULL)
461                 continue;
462         }
463         snprintf(path, length, "%s/%s/%s/%s", innconf->patharticles, priv.topde->d_name, priv.secde->d_name, priv.terde->d_name);
464         if ((priv.artdir = opendir(path)) == NULL)
465             continue;
466     }
467     if (de == NULL)
468         return NULL;
469     snprintf(path, length, "%s/%s/%s/%s/%s", innconf->patharticles, priv.topde->d_name, priv.secde->d_name, priv.terde->d_name, de->d_name);
470
471     art = OpenArticle(path, amount);
472     if (art == (ARTHANDLE *)NULL) {
473         art = xmalloc(sizeof(ARTHANDLE));
474         art->type = TOKEN_TIMEHASH;
475         art->data = NULL;
476         art->len = 0;
477         art->private = xmalloc(sizeof(PRIV_TIMEHASH));
478         newpriv = (PRIV_TIMEHASH *)art->private;
479         newpriv->base = NULL;
480     }
481     newpriv = (PRIV_TIMEHASH *)art->private;
482     newpriv->top = priv.top;
483     newpriv->sec = priv.sec;
484     newpriv->ter = priv.ter;
485     newpriv->artdir = priv.artdir;
486     newpriv->topde = priv.topde;
487     newpriv->secde = priv.secde;
488     newpriv->terde = priv.terde;
489     snprintf(path, length, "%s/%s/%s/%s", priv.topde->d_name, priv.secde->d_name, priv.terde->d_name, de->d_name);
490     art->token = PathToToken(path);
491     BreakToken(*art->token, (int *)&(art->arrived), &seqnum);
492     free(path);
493     return art;
494 }
495
496 bool timehash_ctl(PROBETYPE type, TOKEN *token UNUSED, void *value) {
497     struct artngnum *ann;
498
499     switch (type) {
500     case SMARTNGNUM:
501         if ((ann = (struct artngnum *)value) == NULL)
502             return false;
503         /* make SMprobe() call timehash_retrieve() */
504         ann->artnum = 0;
505         return true;
506     default:
507         return false;
508     }
509 }
510
511 bool
512 timehash_flushcacheddata(FLUSHTYPE type UNUSED)
513 {
514     return true;
515 }
516
517 void
518 timehash_printfiles(FILE *file, TOKEN token, char **xref UNUSED,
519                     int ngroups UNUSED)
520 {
521     int now, seqnum;
522     char *path;
523     
524     BreakToken(token, &now, &seqnum);
525     path = MakePath(now, seqnum, token.class);
526     fprintf(file, "%s\n", path);
527 }
528
529 void timehash_shutdown(void) {
530 }