chiark / gitweb /
Commit 2.4.5-5 as unpacked
[innduct.git] / storage / interface.c
1 /*  $Id: interface.c 7277 2005-06-07 04:40:16Z eagle $
2 **
3 **  Storage Manager interface
4 */
5 #include "config.h"
6 #include "clibrary.h"
7 #include <ctype.h>
8 #include <errno.h>
9 #include <syslog.h>
10 #include <time.h>
11
12 #include "conffile.h"
13 #include "inn/innconf.h"
14 #include "inn/wire.h"
15 #include "interface.h"
16 #include "libinn.h"
17 #include "methods.h"
18 #include "paths.h"
19
20 typedef enum {INIT_NO, INIT_DONE, INIT_FAIL} INITTYPE;
21 typedef struct {
22     INITTYPE            initialized;
23     bool                configured;
24     bool                selfexpire;
25     bool                expensivestat;
26 } METHOD_DATA;
27
28 METHOD_DATA method_data[NUM_STORAGE_METHODS];
29
30 static STORAGE_SUB      *subscriptions = NULL;
31 static unsigned int     typetoindex[256];
32 int                     SMerrno;
33 char                    *SMerrorstr = NULL;
34 static bool             ErrorAlloc = false;
35 static bool             Initialized = false;
36 bool                    SMopenmode = false;
37 bool                    SMpreopen = false;
38
39 /*
40 ** Checks to see if the token is valid
41 */
42 bool IsToken(const char *text) {
43     const char          *p;
44     
45     if (!text)
46         return false;
47     
48     if (strlen(text) != (sizeof(TOKEN) * 2) + 2)
49         return false;
50     
51     if (text[0] != '@')
52         return false;
53
54     if (text[(sizeof(TOKEN) * 2) + 1] != '@')
55         return false;
56
57     for (p = text + 1; *p != '@'; p++)
58         if (!isxdigit((int)*p))
59             return false;
60     
61     return true;
62 }
63
64 /*
65 ** Converts a token to a textual representation for error messages
66 ** and the like.
67 */
68 char *
69 TokenToText(const TOKEN token)
70 {
71     static const char   hex[] = "0123456789ABCDEF";
72     static char         result[(sizeof(TOKEN) * 2) + 3];
73     const char          *p;
74     char                *q;
75     size_t              i;
76
77     
78     result[0] = '@';
79     for (q = result + 1, p = (const char *) &token, i = 0; i < sizeof(TOKEN);
80          i++, p++) {
81         *q++ = hex[(*p & 0xF0) >> 4];
82         *q++ = hex[*p & 0x0F];
83     }
84     *q++ = '@';
85     *q++ = '\0';
86     return result;
87     
88 }
89
90 /*
91 ** Converts a hex digit and converts it to a int
92 */
93 static int hextodec(const int c) {
94     return isdigit(c) ? (c - '0') : ((c - 'A') + 10);
95 }
96
97 /*
98 ** Converts a textual representation of a token back to a native
99 ** representation
100 */
101 TOKEN TextToToken(const char *text) {
102     const char          *p;
103     char                *q;
104     int                 i;
105     TOKEN               token;
106
107     if (text[0] == '@')
108         p = &text[1];
109     else
110         p = text;
111
112     for (q = (char *)&token, i = 0; i != sizeof(TOKEN); i++) {
113         q[i] = (hextodec(*p) << 4) + hextodec(*(p + 1));
114         p += 2;
115     }
116     return token;
117 }
118
119 /*
120 ** Given an article and length in non-wire format, return a malloced region
121 ** containing the article in wire format.  Set *newlen to the length of the
122 ** new article.
123 */ 
124 char *
125 ToWireFmt(const char *article, size_t len, size_t *newlen)
126 {
127     size_t bytes;
128     char *newart;
129     const char *p;
130     char  *dest;
131     bool atstartofline=true;
132
133     /* First go thru article and count number of bytes we need. */
134     for (bytes = 0, p=article ; p < &article[len] ; ++p) {
135         if (*p == '.' && atstartofline) ++bytes; /* 1 byte for escaping . */
136         ++bytes;
137         if (*p == '\n') {
138             ++bytes; /* need another byte for CR */
139             atstartofline = true; /* next char starts new line */
140         } else {
141             atstartofline = false;
142         }
143     }
144     bytes += 3; /* for .\r\n */
145     newart = xmalloc(bytes + 1);
146     *newlen = bytes;
147
148     /* now copy the article, making changes */
149     atstartofline = true;
150     for (p=article, dest=newart ; p < &article[len] ; ++p) {
151         if (*p == '\n') {
152             *dest++ = '\r';
153             *dest++ = '\n';
154             atstartofline = true;
155         } else {
156             if (atstartofline && *p == '.') *dest++ = '.'; /* add extra . */
157             *dest++ = *p;
158             atstartofline = false;
159         }
160     }
161     *dest++ = '.';
162     *dest++ = '\r';
163     *dest++ = '\n';
164     *dest = '\0';
165     return newart;
166 }
167
168 char *
169 FromWireFmt(const char *article, size_t len, size_t *newlen)
170 {
171     size_t bytes;
172     char *newart;
173     const char *p;
174     char *dest;
175     bool atstartofline = true;
176
177     /* First go thru article and count number of bytes we need */
178     for (bytes = 0, p=article ; p < &article[len] ; ) {
179         /* check for terminating .\r\n and if so break */
180         if (p == &article[len-3] && *p == '.' && p[1] == '\r' && p[2] == '\n')
181             break;
182         /* check for .. at start-of-line */
183         if (atstartofline && p < &article[len-1] && *p == '.' && p[1] == '.') {
184             bytes++; /* only output 1 byte */
185             p+=2; 
186             atstartofline = false;
187         } else if (p < &article[len-1] && *p == '\r' && p[1] == '\n') { 
188             bytes++; /* \r\n counts as only one byte in output */
189             p += 2;
190             atstartofline = true;
191         } else {
192             bytes++;
193             p++;
194             atstartofline = false;
195         }
196     }
197     newart = xmalloc(bytes + 1);
198     *newlen = bytes;
199     for (p = article, dest = newart ; p < &article[len]; ) {
200         /* check for terminating .\r\n and if so break */
201         if (p == &article[len-3] && *p == '.' && p[1] == '\r' && p[2] == '\n')
202             break;
203         if (atstartofline && p < &article[len-1] && *p == '.' && p[1] == '.') {
204             *dest++ = '.';
205             p += 2;
206             atstartofline = false;
207         } else if (p < &article[len-1] && *p == '\r' && p[1] == '\n') {
208             *dest++ = '\n';
209             p += 2;
210             atstartofline = true;
211         } else {
212             *dest++ = *p++;
213             atstartofline = false;
214         }
215     }
216     *dest = '\0';
217     return newart;
218 }
219
220 /*
221 **  get Xref header without pathhost
222 */
223 static char *
224 GetXref(ARTHANDLE *art)
225 {
226   const char *p, *p1;
227   const char *q;
228   char  *buff;
229   bool  Nocr = false;
230
231   p = wire_findheader(art->data, art->len, "xref");
232   if (p == NULL)
233     return NULL;
234   q = p;
235   for (p1 = NULL; p < art->data + art->len; p++) {
236     if (p1 != (char *)NULL && *p1 == '\r' && *p == '\n') {
237       Nocr = false;
238       break;
239     }
240     if (*p == '\n') {
241       Nocr = true;
242       break;
243     }
244     p1 = p;
245   }
246   if (p >= art->data + art->len)
247     return NULL;
248   if (!Nocr)
249     p = p1;
250   /* skip pathhost */
251   for (; (*q == ' ') && (q < p); q++);
252   if (q == p)
253     return NULL;
254   if ((q = memchr(q, ' ', p - q)) == NULL)
255     return NULL;
256   for (q++; (*q == ' ') && (q < p); q++);
257   if (q == p)
258     return NULL;
259   buff = xmalloc(p - q + 1);
260   memcpy(buff, q, p - q);
261   buff[p - q] = '\0';
262   return buff;
263 }
264
265 /*
266 **  Split newsgroup and returns artnum
267 **  or 0 if there are no newsgroup.
268 */
269 static ARTNUM GetGroups(char *Xref) {
270   char  *p;
271
272   if ((p = strchr(Xref, ':')) == NULL)
273     return 0;
274   *p++ = '\0';
275   return ((ARTNUM)atoi(p));
276 }
277
278 STORAGE_SUB *SMGetConfig(STORAGETYPE type, STORAGE_SUB *sub) {
279     if (sub == (STORAGE_SUB *)NULL)
280         sub = subscriptions;
281     else
282         sub = sub->next;
283     for (;sub != NULL; sub = sub->next) {
284         if (sub->type == type) {
285             return sub;
286         }
287     }
288     return (STORAGE_SUB *)NULL;
289 }
290
291 static time_t ParseTime(char *tmbuf)
292 {
293     char *startnum;
294     time_t ret;
295     int tmp;
296
297     ret = 0;
298     startnum = tmbuf;
299     while (*tmbuf) {
300         if (!isdigit((int)*tmbuf)) {
301             tmp = atol(startnum);
302             switch (*tmbuf) {
303               case 'M':
304                 ret += tmp*60*60*24*31;
305                 break;
306               case 'd':
307                 ret += tmp*60*60*24;
308                 break;
309               case 'h':
310                 ret += tmp*60*60;
311                 break;
312               case 'm':
313                 ret += tmp*60;
314                 break;
315               case 's':
316                 ret += tmp;
317                 break;
318               default:
319                 return(0);
320             }
321             startnum = tmbuf+1;
322         }
323         tmbuf++;
324     }
325     return(ret);
326 }
327
328 #define SMlbrace  1
329 #define SMrbrace  2
330 #define SMmethod  10
331 #define SMgroups  11
332 #define SMsize    12
333 #define SMclass   13
334 #define SMexpire  14
335 #define SMoptions 15
336 #define SMexactmatch 16
337
338 static CONFTOKEN smtoks[] = {
339   { SMlbrace,   "{" },
340   { SMrbrace,   "}" },
341   { SMmethod,   "method" },
342   { SMgroups,   "newsgroups:" },
343   { SMsize,     "size:" },
344   { SMclass,    "class:" },
345   { SMexpire,   "expires:" },
346   { SMoptions,  "options:" },
347   { SMexactmatch,       "exactmatch:" },
348   { 0, 0 }
349 };
350
351 /* Open the config file and parse it, generating the policy data */
352 static bool
353 SMreadconfig(void)
354 {
355     CONFFILE            *f;
356     CONFTOKEN           *tok;
357     int                 type;
358     int                 i;
359     char                *p;
360     char                *q;
361     char                *path;
362     char                *method = NULL;
363     char                *pattern = NULL;
364     size_t              minsize = 0;
365     size_t              maxsize = 0;
366     time_t              minexpire = 0;
367     time_t              maxexpire = 0;
368     int                 class = 0;
369     STORAGE_SUB         *sub = NULL;
370     STORAGE_SUB         *prev = NULL;
371     char                *options = 0;
372     int                 inbrace;
373     bool                exactmatch = false;
374
375     /* if innconf isn't already read in, do so. */
376     if (innconf == NULL) {
377         if (!innconf_read(NULL)) {
378             SMseterror(SMERR_INTERNAL, "ReadInnConf() failed");
379             return false;
380         }
381     }
382
383     for (i = 0; i < NUM_STORAGE_METHODS; i++) {
384         method_data[i].initialized = INIT_NO;
385         method_data[i].configured = false;
386     }
387     path = concatpath(innconf->pathetc, _PATH_STORAGECTL);
388     f = CONFfopen(path);
389     if (f == NULL) {
390         SMseterror(SMERR_UNDEFINED, NULL);
391         syslog(L_ERROR, "SM Could not open %s: %m", path);
392         free(path);
393         return false;
394     }
395     free(path);
396     
397     inbrace = 0;
398     while ((tok = CONFgettoken(smtoks, f)) != NULL) {
399         if (!inbrace) {
400             if (tok->type != SMmethod) {
401                 SMseterror(SMERR_CONFIG, "Expected 'method' keyword");
402                 syslog(L_ERROR, "SM expected 'method' keyword, line %d", f->lineno);
403                 return false;
404             }
405             if ((tok = CONFgettoken(0, f)) == NULL) {
406                 SMseterror(SMERR_CONFIG, "Expected method name");
407                 syslog(L_ERROR, "SM expected method name, line %d", f->lineno);
408                 return false;
409             }
410             method = xstrdup(tok->name);
411             if ((tok = CONFgettoken(smtoks, f)) == NULL || tok->type != SMlbrace) {
412                 SMseterror(SMERR_CONFIG, "Expected '{'");
413                 syslog(L_ERROR, "SM Expected '{', line %d", f->lineno);
414                 return false;
415             }
416             inbrace = 1;
417             /* initialize various params to defaults. */
418             minsize = 0;
419             maxsize = 0; /* zero means no limit */
420             class = 0;
421             pattern = NULL;
422             options = NULL;
423             minexpire = 0;
424             maxexpire = 0;
425             exactmatch = false;
426
427         } else {
428             type = tok->type;
429             if (type == SMrbrace)
430                 inbrace = 0;
431             else {
432                 if ((tok = CONFgettoken(0, f)) == NULL) {
433                     SMseterror(SMERR_CONFIG, "Keyword with no value");
434                     syslog(L_ERROR, "SM keyword with no value, line %d", f->lineno);
435                     return false;
436                 }
437                 p = tok->name;
438                 switch(type) {
439                   case SMgroups:
440                     if (pattern)
441                         free(pattern);
442                     pattern = xstrdup(tok->name);
443                     break;
444                   case SMsize:
445                     minsize = strtoul(p, NULL, 10);
446                     if ((p = strchr(p, ',')) != NULL) {
447                         p++;
448                         maxsize = strtoul(p, NULL, 10);
449                     }
450                     break;
451                   case SMclass:
452                     class = atoi(p);
453                     if (class > NUM_STORAGE_CLASSES) {
454                         SMseterror(SMERR_CONFIG, "Storage class too large");
455                         warn("SM: storage class larger than %d, line %d",
456                              NUM_STORAGE_CLASSES, f->lineno);
457                         return false;
458                     }
459                     break;
460                   case SMexpire:
461                     q = strchr(p, ',');
462                     if (q)
463                         *q++ = 0;
464                     minexpire = ParseTime(p);
465                     if (q)
466                         maxexpire = ParseTime(q);
467                     break;
468                   case SMoptions:
469                     if (options)
470                         free(options);
471                     options = xstrdup(p);
472                     break;
473                   case SMexactmatch:
474                     if (strcasecmp(p, "true") == 0
475                         || strcasecmp(p, "yes") == 0
476                         || strcasecmp(p, "on") == 0)
477                         exactmatch = true;
478                     break;
479                   default:
480                     SMseterror(SMERR_CONFIG, "Unknown keyword in method declaration");
481                     syslog(L_ERROR, "SM Unknown keyword in method declaration, line %d: %s", f->lineno, tok->name);
482                     free(method);
483                     return false;
484                     break;
485                 }
486             }
487         }
488         if (!inbrace) {
489             /* just finished a declaration */
490             sub = xmalloc(sizeof(STORAGE_SUB));
491             sub->type = TOKEN_EMPTY;
492             for (i = 0; i < NUM_STORAGE_METHODS; i++) {
493                 if (!strcasecmp(method, storage_methods[i].name)) {
494                     sub->type = storage_methods[i].type;
495                     method_data[i].configured = true;
496                     break;
497                 }
498             }
499             if (sub->type == TOKEN_EMPTY) {
500                 SMseterror(SMERR_CONFIG, "Invalid storage method name");
501                 syslog(L_ERROR, "SM no configured storage methods are named '%s'", method);
502                 free(options);
503                 free(sub);
504                 return false;
505             }
506             if (!pattern) {
507                 SMseterror(SMERR_CONFIG, "pattern not defined");
508                 syslog(L_ERROR, "SM no pattern defined");
509                 free(options);
510                 free(sub);
511                 return false;
512             }
513             sub->pattern = pattern;
514             sub->minsize = minsize;
515             sub->maxsize = maxsize;
516             sub->class = class;
517             sub->options = options;
518             sub->minexpire = minexpire;
519             sub->maxexpire = maxexpire;
520             sub->exactmatch = exactmatch;
521
522             free(method);
523             method = 0;
524
525             if (!prev)
526                 subscriptions = sub;
527             if (prev)
528                 prev->next = sub;
529             prev = sub;
530             sub->next = NULL;
531         }
532     }
533     
534     CONFfclose(f);
535
536     return true;
537 }
538
539 /*
540 ** setup storage api environment (open mode etc.)
541 */
542 bool SMsetup(SMSETUP type, void *value) {
543     if (Initialized)    
544         return false;
545     switch (type) {
546     case SM_RDWR:
547         SMopenmode = *(bool *)value;
548         break;
549     case SM_PREOPEN:
550         SMpreopen = *(bool *)value;
551         break;
552     default:
553         return false;
554     }
555     return true;
556 }
557
558 /*
559 ** Calls the setup function for all of the configured methods and returns
560 ** true if they all initialize ok, false if they don't
561 */
562 bool SMinit(void) {
563     int                 i;
564     bool                allok = true;
565     static              bool once = false;
566     SMATTRIBUTE         smattr;
567
568     if (Initialized)
569         return true;
570     
571     Initialized = true;
572     
573     if (!SMreadconfig()) {
574         SMshutdown();
575         Initialized = false;
576         return false;
577     }
578
579     for (i = 0; i < NUM_STORAGE_METHODS; i++) {
580         if (method_data[i].configured) {
581             if (method_data[i].configured && storage_methods[i].init(&smattr)) {
582                 method_data[i].initialized = INIT_DONE;
583                 method_data[i].selfexpire = smattr.selfexpire;
584                 method_data[i].expensivestat = smattr.expensivestat;
585             } else {
586                 method_data[i].initialized = INIT_FAIL;
587                 method_data[i].selfexpire = false;
588                 method_data[i].expensivestat = true;
589                 syslog(L_ERROR, "SM storage method '%s' failed initialization", storage_methods[i].name);
590                 allok = false;
591             }
592         }
593         typetoindex[storage_methods[i].type] = i;
594     }
595     if (!allok) {
596         SMshutdown();
597         Initialized = false;
598         SMseterror(SMERR_UNDEFINED, "one or more storage methods failed initialization");
599         syslog(L_ERROR, "SM one or more storage methods failed initialization");
600         return false;
601     }
602     if (!once && atexit(SMshutdown) < 0) {
603         SMshutdown();
604         Initialized = false;
605         SMseterror(SMERR_UNDEFINED, NULL);
606         return false;
607     }
608     once = true;
609     return true;
610 }
611
612 static bool InitMethod(STORAGETYPE method) {
613     SMATTRIBUTE         smattr;
614
615     if (!Initialized)
616         if (!SMreadconfig()) {
617             Initialized = false;
618             return false;
619         }
620     Initialized = true;
621     
622     if (method_data[method].initialized == INIT_DONE)
623         return true;
624
625     if (method_data[method].initialized == INIT_FAIL)
626         return false;
627
628     if (!method_data[method].configured) {
629         method_data[method].initialized = INIT_FAIL;
630         SMseterror(SMERR_UNDEFINED, "storage method is not configured.");
631         return false;
632     }
633     if (!storage_methods[method].init(&smattr)) {
634         method_data[method].initialized = INIT_FAIL;
635         method_data[method].selfexpire = false;
636         method_data[method].expensivestat = true;
637         SMseterror(SMERR_UNDEFINED, "Could not initialize storage method late.");
638         return false;
639     }
640     method_data[method].initialized = INIT_DONE;
641     method_data[method].selfexpire = smattr.selfexpire;
642     method_data[method].expensivestat = smattr.expensivestat;
643     return true;
644 }
645
646 static bool
647 MatchGroups(const char *g, int len, const char *pattern, bool exactmatch)
648 {
649     char *group, *groups, *q;
650     int i, lastwhite;
651     enum uwildmat matched;
652     bool wanted = false;
653
654     q = groups = xmalloc(len + 1);
655     for (lastwhite = -1,  i = 0 ; i < len ; i++) {
656         /* trim white chars */
657         if (g[i] == '\r' || g[i] == '\n' || g[i] == ' ' || g[i] == '\t') {
658             if (lastwhite + 1 != i)
659                 *q++ = ' ';
660             lastwhite = i;
661         } else
662             *q++ = g[i];
663     }
664     *q = '\0';
665
666     group = strtok(groups, " ,");
667     while (group != NULL) {
668         q = strchr(group, ':');
669         if (q != NULL)
670             *q = '\0';
671         matched = uwildmat_poison(group, pattern);
672         if (matched == UWILDMAT_POISON || (exactmatch && !matched)) {
673             free(groups);
674             return false;
675         }
676         if (matched == UWILDMAT_MATCH)
677             wanted = true;
678         group = strtok(NULL, " ,");
679     }
680
681     free(groups);
682     return wanted;
683 }
684
685 STORAGE_SUB *SMgetsub(const ARTHANDLE article) {
686     STORAGE_SUB         *sub;
687
688     if (article.len == 0) {
689         SMseterror(SMERR_BADHANDLE, NULL);
690         return NULL;
691     }
692
693     if (article.groups == NULL)
694         return NULL;
695
696     for (sub = subscriptions; sub != NULL; sub = sub->next) {
697         if (!(method_data[typetoindex[sub->type]].initialized == INIT_FAIL) &&
698             (article.len >= sub->minsize) &&
699             (!sub->maxsize || (article.len <= sub->maxsize)) &&
700             (!sub->minexpire || article.expires >= sub->minexpire) &&
701             (!sub->maxexpire || (article.expires <= sub->maxexpire)) &&
702             MatchGroups(article.groups, article.groupslen, sub->pattern,
703                         sub->exactmatch)) {
704             if (InitMethod(typetoindex[sub->type]))
705                 return sub;
706         }
707     }
708     errno = 0;
709     SMseterror(SMERR_NOMATCH, "no matching entry in storage.conf");
710     return NULL;
711 }
712
713 TOKEN SMstore(const ARTHANDLE article) {
714     STORAGE_SUB         *sub;
715     TOKEN               result;
716
717     if (!SMopenmode) {
718         result.type = TOKEN_EMPTY;
719         SMseterror(SMERR_INTERNAL, "read only storage api");
720         return result;
721     }
722     result.type = TOKEN_EMPTY;
723     if ((sub = SMgetsub(article)) == NULL) {
724         return result;
725     }
726     return storage_methods[typetoindex[sub->type]].store(article, sub->class);
727 }
728
729 ARTHANDLE *SMretrieve(const TOKEN token, const RETRTYPE amount) {
730     ARTHANDLE           *art;
731
732     if (method_data[typetoindex[token.type]].initialized == INIT_FAIL) {
733         SMseterror(SMERR_UNINIT, NULL);
734         return NULL;
735     }
736     if (method_data[typetoindex[token.type]].initialized == INIT_NO && !InitMethod(typetoindex[token.type])) {
737         syslog(L_ERROR, "SM could not find token type or method was not initialized (%d)",
738                token.type);
739         SMseterror(SMERR_UNINIT, NULL);
740         return NULL;
741     }
742     art = storage_methods[typetoindex[token.type]].retrieve(token, amount);
743     if (art)
744         art->nextmethod = 0;
745     return art;
746
747 }
748
749 ARTHANDLE *SMnext(const ARTHANDLE *article, const RETRTYPE amount) {
750     unsigned char       i;
751     int                 start;
752     ARTHANDLE           *newart;
753
754     if (article == NULL)
755         start = 0;
756     else
757         start= article->nextmethod;
758
759     if (method_data[start].initialized == INIT_FAIL) {
760         SMseterror(SMERR_UNINIT, NULL);
761         return NULL;
762     }
763     if (method_data[start].initialized == INIT_NO && method_data[start].configured
764       && !InitMethod(start)) {
765         SMseterror(SMERR_UNINIT, NULL);
766         return NULL;
767     }
768
769     for (i = start, newart = NULL; i < NUM_STORAGE_METHODS; i++) {
770         if (method_data[i].configured && (newart = storage_methods[i].next(article, amount)) != (ARTHANDLE *)NULL) {
771             newart->nextmethod = i;
772             break;
773         } else
774             article = NULL;
775     }
776
777     return newart;
778 }
779
780 void SMfreearticle(ARTHANDLE *article) {
781     if (method_data[typetoindex[article->type]].initialized == INIT_FAIL) {
782         return;
783     }
784     if (method_data[typetoindex[article->type]].initialized == INIT_NO && !InitMethod(typetoindex[article->type])) {
785         syslog(L_ERROR, "SM can't free article with uninitialized method");
786         return;
787     }
788     storage_methods[typetoindex[article->type]].freearticle(article);
789 }
790
791 bool SMcancel(TOKEN token) {
792     if (!SMopenmode) {
793         SMseterror(SMERR_INTERNAL, "read only storage api");
794         return false;
795     }
796     if (method_data[typetoindex[token.type]].initialized == INIT_FAIL) {
797         SMseterror(SMERR_UNINIT, NULL);
798         return false;
799     }
800     if (method_data[typetoindex[token.type]].initialized == INIT_NO && !InitMethod(typetoindex[token.type])) {
801         SMseterror(SMERR_UNINIT, NULL);
802         syslog(L_ERROR, "SM can't cancel article with uninitialized method");
803         return false;
804     }
805     return storage_methods[typetoindex[token.type]].cancel(token);
806 }
807
808 bool SMprobe(PROBETYPE type, TOKEN *token, void *value) {
809     struct artngnum     *ann;
810     ARTHANDLE           *art;
811
812     switch (type) {
813     case SELFEXPIRE:
814         return (method_data[typetoindex[token->type]].selfexpire);
815     case SMARTNGNUM:
816         if (method_data[typetoindex[token->type]].initialized == INIT_FAIL) {
817             SMseterror(SMERR_UNINIT, NULL);
818             return false;
819         }
820         if (method_data[typetoindex[token->type]].initialized == INIT_NO && !InitMethod(typetoindex[token->type])) {
821             SMseterror(SMERR_UNINIT, NULL);
822             syslog(L_ERROR, "SM can't cancel article with uninitialized method");
823             return false;
824         }
825         if ((ann = (struct artngnum *)value) == NULL)
826             return false;
827         ann->groupname = NULL;
828         if (storage_methods[typetoindex[token->type]].ctl(type, token, value)) {
829             if (ann->artnum != 0) {
830                 /* set by storage method */
831                 return true;
832             } else {
833                 art = storage_methods[typetoindex[token->type]].retrieve(*token, RETR_HEAD);
834                 if (art == NULL) {
835                     if (ann->groupname != NULL)
836                         free(ann->groupname);
837                     storage_methods[typetoindex[token->type]].freearticle(art);
838                     return false;
839                 }
840                 if ((ann->groupname = GetXref(art)) == NULL) {
841                     if (ann->groupname != NULL)
842                         free(ann->groupname);
843                     storage_methods[typetoindex[token->type]].freearticle(art);
844                     return false;
845                 }
846                 storage_methods[typetoindex[token->type]].freearticle(art);
847                 if ((ann->artnum = GetGroups(ann->groupname)) == 0) {
848                     if (ann->groupname != NULL)
849                         free(ann->groupname);
850                     return false;
851                 }
852                 return true;
853             }
854         } else {
855             return false;
856         }
857     case EXPENSIVESTAT:
858         return (method_data[typetoindex[token->type]].expensivestat);
859     default:
860         return false;
861     }
862 }
863
864 bool SMflushcacheddata(FLUSHTYPE type) {
865     int         i;
866
867     for (i = 0; i < NUM_STORAGE_METHODS; i++) {
868         if (method_data[i].initialized == INIT_DONE &&
869             !storage_methods[i].flushcacheddata(type))
870             syslog(L_ERROR, "SM can't flush cached data method '%s'", storage_methods[i].name);
871     }
872     return true;
873 }
874
875 void SMprintfiles(FILE *file, TOKEN token, char **xref, int ngroups) {
876     if (method_data[typetoindex[token.type]].initialized == INIT_FAIL)
877         return;
878     if (method_data[typetoindex[token.type]].initialized == INIT_NO
879         && !InitMethod(typetoindex[token.type])) {
880         SMseterror(SMERR_UNINIT, NULL);
881         syslog(L_ERROR, "SM can't print files for article with uninitialized method");
882         return;
883     }
884     storage_methods[typetoindex[token.type]].printfiles(file, token, xref, ngroups);
885 }
886
887 void SMshutdown(void) {
888     int                 i;
889     STORAGE_SUB         *old;
890
891     if (!Initialized)
892         return;
893
894     for (i = 0; i < NUM_STORAGE_METHODS; i++)
895         if (method_data[i].initialized == INIT_DONE) {
896             storage_methods[i].shutdown();
897             method_data[i].initialized = INIT_NO;
898             method_data[i].configured = false;
899         }
900     while (subscriptions) {
901         old = subscriptions;
902         subscriptions = subscriptions->next;
903         free(old->pattern);
904         free(old->options);
905         free(old);
906     }
907     Initialized = false;
908 }
909
910 void SMseterror(int errornum, char *error) {
911     if (ErrorAlloc)
912         free(SMerrorstr);
913
914     ErrorAlloc = false;
915     
916     if ((errornum == SMERR_UNDEFINED) && (errno == ENOENT))
917         errornum = SMERR_NOENT;
918             
919     SMerrno = errornum;
920
921     if (error == NULL) {
922         switch (SMerrno) {
923         case SMERR_UNDEFINED:
924             SMerrorstr = xstrdup(strerror(errno));
925             ErrorAlloc = true;
926             break;
927         case SMERR_INTERNAL:
928             SMerrorstr = "Internal error";
929             break;
930         case SMERR_NOENT:
931             SMerrorstr = "Token not found";
932             break;
933         case SMERR_TOKENSHORT:
934             SMerrorstr = "Configured token size too small";
935             break;
936         case SMERR_NOBODY:
937             SMerrorstr = "No article body found";
938             break;
939         case SMERR_UNINIT:
940             SMerrorstr = "Storage manager is not initialized";
941             break;
942         case SMERR_CONFIG:
943             SMerrorstr = "Error reading config file";
944             break;
945         case SMERR_BADHANDLE:
946             SMerrorstr = "Bad article handle";
947             break;
948         case SMERR_BADTOKEN:
949             SMerrorstr = "Bad token";
950             break;
951         case SMERR_NOMATCH:
952             SMerrorstr = "No matching entry in storage.conf";
953             break;
954         default:
955             SMerrorstr = "Undefined error";
956         }
957     } else {
958         SMerrorstr = xstrdup(error);
959         ErrorAlloc = true;
960     }
961 }
962