1 /* $Id: interface.c 7277 2005-06-07 04:40:16Z eagle $
3 ** Storage Manager interface
13 #include "inn/innconf.h"
15 #include "interface.h"
20 typedef enum {INIT_NO, INIT_DONE, INIT_FAIL} INITTYPE;
28 METHOD_DATA method_data[NUM_STORAGE_METHODS];
30 static STORAGE_SUB *subscriptions = NULL;
31 static unsigned int typetoindex[256];
33 char *SMerrorstr = NULL;
34 static bool ErrorAlloc = false;
35 static bool Initialized = false;
36 bool SMopenmode = false;
37 bool SMpreopen = false;
40 ** Checks to see if the token is valid
42 bool IsToken(const char *text) {
48 if (strlen(text) != (sizeof(TOKEN) * 2) + 2)
54 if (text[(sizeof(TOKEN) * 2) + 1] != '@')
57 for (p = text + 1; *p != '@'; p++)
58 if (!isxdigit((int)*p))
65 ** Converts a token to a textual representation for error messages
69 TokenToText(const TOKEN token)
71 static const char hex[] = "0123456789ABCDEF";
72 static char result[(sizeof(TOKEN) * 2) + 3];
79 for (q = result + 1, p = (const char *) &token, i = 0; i < sizeof(TOKEN);
81 *q++ = hex[(*p & 0xF0) >> 4];
82 *q++ = hex[*p & 0x0F];
91 ** Converts a hex digit and converts it to a int
93 static int hextodec(const int c) {
94 return isdigit(c) ? (c - '0') : ((c - 'A') + 10);
98 ** Converts a textual representation of a token back to a native
101 TOKEN TextToToken(const char *text) {
112 for (q = (char *)&token, i = 0; i != sizeof(TOKEN); i++) {
113 q[i] = (hextodec(*p) << 4) + hextodec(*(p + 1));
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
125 ToWireFmt(const char *article, size_t len, size_t *newlen)
131 bool atstartofline=true;
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 . */
138 ++bytes; /* need another byte for CR */
139 atstartofline = true; /* next char starts new line */
141 atstartofline = false;
144 bytes += 3; /* for .\r\n */
145 newart = xmalloc(bytes + 1);
148 /* now copy the article, making changes */
149 atstartofline = true;
150 for (p=article, dest=newart ; p < &article[len] ; ++p) {
154 atstartofline = true;
156 if (atstartofline && *p == '.') *dest++ = '.'; /* add extra . */
158 atstartofline = false;
169 FromWireFmt(const char *article, size_t len, size_t *newlen)
175 bool atstartofline = true;
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')
182 /* check for .. at start-of-line */
183 if (atstartofline && p < &article[len-1] && *p == '.' && p[1] == '.') {
184 bytes++; /* only output 1 byte */
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 */
190 atstartofline = true;
194 atstartofline = false;
197 newart = xmalloc(bytes + 1);
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')
203 if (atstartofline && p < &article[len-1] && *p == '.' && p[1] == '.') {
206 atstartofline = false;
207 } else if (p < &article[len-1] && *p == '\r' && p[1] == '\n') {
210 atstartofline = true;
213 atstartofline = false;
221 ** get Xref header without pathhost
224 GetXref(ARTHANDLE *art)
231 p = wire_findheader(art->data, art->len, "xref");
235 for (p1 = NULL; p < art->data + art->len; p++) {
236 if (p1 != (char *)NULL && *p1 == '\r' && *p == '\n') {
246 if (p >= art->data + art->len)
251 for (; (*q == ' ') && (q < p); q++);
254 if ((q = memchr(q, ' ', p - q)) == NULL)
256 for (q++; (*q == ' ') && (q < p); q++);
259 buff = xmalloc(p - q + 1);
260 memcpy(buff, q, p - q);
266 ** Split newsgroup and returns artnum
267 ** or 0 if there are no newsgroup.
269 static ARTNUM GetGroups(char *Xref) {
272 if ((p = strchr(Xref, ':')) == NULL)
275 return ((ARTNUM)atoi(p));
278 STORAGE_SUB *SMGetConfig(STORAGETYPE type, STORAGE_SUB *sub) {
279 if (sub == (STORAGE_SUB *)NULL)
283 for (;sub != NULL; sub = sub->next) {
284 if (sub->type == type) {
288 return (STORAGE_SUB *)NULL;
291 static time_t ParseTime(char *tmbuf)
300 if (!isdigit((int)*tmbuf)) {
301 tmp = atol(startnum);
304 ret += tmp*60*60*24*31;
336 #define SMexactmatch 16
338 static CONFTOKEN smtoks[] = {
341 { SMmethod, "method" },
342 { SMgroups, "newsgroups:" },
344 { SMclass, "class:" },
345 { SMexpire, "expires:" },
346 { SMoptions, "options:" },
347 { SMexactmatch, "exactmatch:" },
351 /* Open the config file and parse it, generating the policy data */
363 char *pattern = NULL;
366 time_t minexpire = 0;
367 time_t maxexpire = 0;
369 STORAGE_SUB *sub = NULL;
370 STORAGE_SUB *prev = NULL;
373 bool exactmatch = false;
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");
383 for (i = 0; i < NUM_STORAGE_METHODS; i++) {
384 method_data[i].initialized = INIT_NO;
385 method_data[i].configured = false;
387 path = concatpath(innconf->pathetc, _PATH_STORAGECTL);
390 SMseterror(SMERR_UNDEFINED, NULL);
391 syslog(L_ERROR, "SM Could not open %s: %m", path);
398 while ((tok = CONFgettoken(smtoks, f)) != NULL) {
400 if (tok->type != SMmethod) {
401 SMseterror(SMERR_CONFIG, "Expected 'method' keyword");
402 syslog(L_ERROR, "SM expected 'method' keyword, line %d", f->lineno);
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);
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);
417 /* initialize various params to defaults. */
419 maxsize = 0; /* zero means no limit */
429 if (type == SMrbrace)
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);
442 pattern = xstrdup(tok->name);
445 minsize = strtoul(p, NULL, 10);
446 if ((p = strchr(p, ',')) != NULL) {
448 maxsize = strtoul(p, NULL, 10);
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);
464 minexpire = ParseTime(p);
466 maxexpire = ParseTime(q);
471 options = xstrdup(p);
474 if (strcasecmp(p, "true") == 0
475 || strcasecmp(p, "yes") == 0
476 || strcasecmp(p, "on") == 0)
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);
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;
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);
507 SMseterror(SMERR_CONFIG, "pattern not defined");
508 syslog(L_ERROR, "SM no pattern defined");
513 sub->pattern = pattern;
514 sub->minsize = minsize;
515 sub->maxsize = maxsize;
517 sub->options = options;
518 sub->minexpire = minexpire;
519 sub->maxexpire = maxexpire;
520 sub->exactmatch = exactmatch;
540 ** setup storage api environment (open mode etc.)
542 bool SMsetup(SMSETUP type, void *value) {
547 SMopenmode = *(bool *)value;
550 SMpreopen = *(bool *)value;
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
565 static bool once = false;
573 if (!SMreadconfig()) {
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;
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);
593 typetoindex[storage_methods[i].type] = i;
598 SMseterror(SMERR_UNDEFINED, "one or more storage methods failed initialization");
599 syslog(L_ERROR, "SM one or more storage methods failed initialization");
602 if (!once && atexit(SMshutdown) < 0) {
605 SMseterror(SMERR_UNDEFINED, NULL);
612 static bool InitMethod(STORAGETYPE method) {
616 if (!SMreadconfig()) {
622 if (method_data[method].initialized == INIT_DONE)
625 if (method_data[method].initialized == INIT_FAIL)
628 if (!method_data[method].configured) {
629 method_data[method].initialized = INIT_FAIL;
630 SMseterror(SMERR_UNDEFINED, "storage method is not configured.");
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.");
640 method_data[method].initialized = INIT_DONE;
641 method_data[method].selfexpire = smattr.selfexpire;
642 method_data[method].expensivestat = smattr.expensivestat;
647 MatchGroups(const char *g, int len, const char *pattern, bool exactmatch)
649 char *group, *groups, *q;
651 enum uwildmat matched;
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)
666 group = strtok(groups, " ,");
667 while (group != NULL) {
668 q = strchr(group, ':');
671 matched = uwildmat_poison(group, pattern);
672 if (matched == UWILDMAT_POISON || (exactmatch && !matched)) {
676 if (matched == UWILDMAT_MATCH)
678 group = strtok(NULL, " ,");
685 STORAGE_SUB *SMgetsub(const ARTHANDLE article) {
688 if (article.len == 0) {
689 SMseterror(SMERR_BADHANDLE, NULL);
693 if (article.groups == NULL)
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,
704 if (InitMethod(typetoindex[sub->type]))
709 SMseterror(SMERR_NOMATCH, "no matching entry in storage.conf");
713 TOKEN SMstore(const ARTHANDLE article) {
718 result.type = TOKEN_EMPTY;
719 SMseterror(SMERR_INTERNAL, "read only storage api");
722 result.type = TOKEN_EMPTY;
723 if ((sub = SMgetsub(article)) == NULL) {
726 return storage_methods[typetoindex[sub->type]].store(article, sub->class);
729 ARTHANDLE *SMretrieve(const TOKEN token, const RETRTYPE amount) {
732 if (method_data[typetoindex[token.type]].initialized == INIT_FAIL) {
733 SMseterror(SMERR_UNINIT, NULL);
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)",
739 SMseterror(SMERR_UNINIT, NULL);
742 art = storage_methods[typetoindex[token.type]].retrieve(token, amount);
749 ARTHANDLE *SMnext(const ARTHANDLE *article, const RETRTYPE amount) {
757 start= article->nextmethod;
759 if (method_data[start].initialized == INIT_FAIL) {
760 SMseterror(SMERR_UNINIT, NULL);
763 if (method_data[start].initialized == INIT_NO && method_data[start].configured
764 && !InitMethod(start)) {
765 SMseterror(SMERR_UNINIT, NULL);
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;
780 void SMfreearticle(ARTHANDLE *article) {
781 if (method_data[typetoindex[article->type]].initialized == INIT_FAIL) {
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");
788 storage_methods[typetoindex[article->type]].freearticle(article);
791 bool SMcancel(TOKEN token) {
793 SMseterror(SMERR_INTERNAL, "read only storage api");
796 if (method_data[typetoindex[token.type]].initialized == INIT_FAIL) {
797 SMseterror(SMERR_UNINIT, NULL);
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");
805 return storage_methods[typetoindex[token.type]].cancel(token);
808 bool SMprobe(PROBETYPE type, TOKEN *token, void *value) {
809 struct artngnum *ann;
814 return (method_data[typetoindex[token->type]].selfexpire);
816 if (method_data[typetoindex[token->type]].initialized == INIT_FAIL) {
817 SMseterror(SMERR_UNINIT, NULL);
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");
825 if ((ann = (struct artngnum *)value) == NULL)
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 */
833 art = storage_methods[typetoindex[token->type]].retrieve(*token, RETR_HEAD);
835 if (ann->groupname != NULL)
836 free(ann->groupname);
837 storage_methods[typetoindex[token->type]].freearticle(art);
840 if ((ann->groupname = GetXref(art)) == NULL) {
841 if (ann->groupname != NULL)
842 free(ann->groupname);
843 storage_methods[typetoindex[token->type]].freearticle(art);
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);
858 return (method_data[typetoindex[token->type]].expensivestat);
864 bool SMflushcacheddata(FLUSHTYPE type) {
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);
875 void SMprintfiles(FILE *file, TOKEN token, char **xref, int ngroups) {
876 if (method_data[typetoindex[token.type]].initialized == INIT_FAIL)
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");
884 storage_methods[typetoindex[token.type]].printfiles(file, token, xref, ngroups);
887 void SMshutdown(void) {
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;
900 while (subscriptions) {
902 subscriptions = subscriptions->next;
910 void SMseterror(int errornum, char *error) {
916 if ((errornum == SMERR_UNDEFINED) && (errno == ENOENT))
917 errornum = SMERR_NOENT;
923 case SMERR_UNDEFINED:
924 SMerrorstr = xstrdup(strerror(errno));
928 SMerrorstr = "Internal error";
931 SMerrorstr = "Token not found";
933 case SMERR_TOKENSHORT:
934 SMerrorstr = "Configured token size too small";
937 SMerrorstr = "No article body found";
940 SMerrorstr = "Storage manager is not initialized";
943 SMerrorstr = "Error reading config file";
945 case SMERR_BADHANDLE:
946 SMerrorstr = "Bad article handle";
949 SMerrorstr = "Bad token";
952 SMerrorstr = "No matching entry in storage.conf";
955 SMerrorstr = "Undefined error";
958 SMerrorstr = xstrdup(error);