/* libutil -- deal with active file Written by Cornelius Krasel . Copyright 2000. Reused some old code written by Arnt Gulbrandsen , copyright 1995, modified by (amongst others) Cornelius Krasel , Randolf Skerka , Kent Robotti and Markus Enzenberger . Copyright for the modifications 1997-1999. Modified and copyright of the modifications by: 2001 - 2002 Matthias Andree 2002 Ralf Wildenhues See file COPYING for restrictions on the use of this software. */ #include "leafnode.h" #include "activutil.h" #include "ln_log.h" #include "mastring.h" #include #include "system.h" #include #include #include #include #include #include #include #ifdef CHECKGROUPORDER #include "ln_log.h" #endif /* CHECKGROUPORDER */ size_t activesize; struct newsgroup *active = NULL; size_t oldactivesize; struct newsgroup *oldactive = NULL; struct nglist { struct newsgroup *entry; struct nglist *next; }; /* warning: this function does not do a deep copy: it does not copy * name or description */ void newsgroup_copy(struct newsgroup *d, const struct newsgroup *s) { d->first = s->first; d->last = s->last; d->age = s->age; d->name = s->name; d->desc = s->desc; } int compactive(const void *a, const void *b) { const struct newsgroup *la = (const struct newsgroup *)a; const struct newsgroup *lb = (const struct newsgroup *)b; return strcasecmp(la->name, lb->name); } static struct nglist *newgroup = NULL; /* * insert a group into a list of groups * if oldactive is set, keep old data of known groups */ void insertgroup(const char *name, long unsigned first, long unsigned last, time_t age) { struct nglist *l; static struct nglist *lold; struct newsgroup *g, *o; char *desc = NULL; g = findgroup(name); if (g) return; if (*name == '.' || strstr(name, "..") || name[strlen(name)-1] == '.') { ln_log(LNLOG_SWARNING, LNLOG_CTOP, "Warning: skipping group \"%s\", " "invalid name (NULL component).", name); return; } if (oldactivesize != 0 && oldactive != NULL) { o = xfindgroup(oldactive, name, oldactivesize); if (o) { last = o->last; first = o->first; if (o->age) age = o->age; if (o->desc) desc = critstrdup(o->desc, "insertgroup"); } } g = (struct newsgroup *)critmalloc(sizeof(struct newsgroup), "Allocating space for new group"); g->name = critstrdup(name, "insertgroup"); g->first = first; g->last = last; g->age = age; g->desc = desc; l = (struct nglist *)critmalloc(sizeof(struct nglist), "Allocating space for newsgroup list"); l->entry = g; l->next = NULL; if (newgroup == NULL) newgroup = l; else lold->next = l; lold = l; } void newgroupdesc(const char *groupname, const char *description) { struct nglist *l = newgroup; while(l) { if (0 == strcasecmp(groupname, l->entry->name)) { if (l->entry->desc) free(l->entry->desc); l->entry->desc = critstrdup(description, "newgroupdesc"); break; } l = l->next; } } /* * change description of newsgroup */ void changegroupdesc(const char *groupname, const char *description) { struct newsgroup *ng; if (groupname && description) { ng = findgroup(groupname); if (ng) { if (ng->desc) free(ng->desc); ng->desc = critstrdup(description, "changegroupdesc"); } } } /* * merge nglist with active group, then free it */ void mergegroups(void) { struct nglist *l, *la; size_t count = 0; #ifdef CHECKGROUPORDER checkgrouporder(); #endif /* CHECKGROUPORDER */ l = newgroup; while (l) { count++; l = l->next; } active = (struct newsgroup *)critrealloc((char *)active, (1 + count + activesize) * sizeof(struct newsgroup), "reallocating active"); l = newgroup; count = activesize; while (l) { la = l; newsgroup_copy(active + count, l->entry); l = l->next; count++; free(la->entry); free(la); /* clean up */ } newgroup = NULL; active[count].name = NULL; activesize = count; qsort(active, activesize, sizeof(struct newsgroup), &compactive); validateactive(); } #ifdef CHECKGROUPORDER void checkgrouporder(void) { unsigned long i; int s = 1; for (i = 1; i < activesize; i++) { if (compactive(&active[i-1], &active[i]) >= 0) { ln_log(LNLOG_SERR, LNLOG_CTOP, "in-core active file misorder at pos. %lu: \"%s\" vs. \"%s\"", i-1, active[i-1].name, active[i].name); break; s = 0; } } } #endif /* CHECKGROUPORDER */ /* * finds a group by name * expects the group list to be sorted in strcasecmp order * does a binary search, recursively implemented */ static long helpfindgroup(struct newsgroup *act, const char *name, long low, long high) { int result; long newp; if (low > high) return -1; newp = (high - low) / 2 + low; result = strcasecmp(name, act[newp].name); if (result == 0) return newp; else if (result < 0) return helpfindgroup(act, name, low, newp - 1); else return helpfindgroup(act, name, newp + 1, high); } /* * find a newsgroup in the active file */ struct newsgroup * xfindgroup(struct newsgroup *act, const char *name, unsigned long actsize) { long i; if (actsize > (unsigned long)LONG_MAX) { syslog(LOG_CRIT, "xfindgroup: count %lu too large (max. %ld), aborting", actsize, LONG_MAX); abort(); } i = helpfindgroup(act, name, 0, actsize - 1); if (i < 0) return NULL; else return (&act[i]); } struct newsgroup * findgroup(const char *name) { #ifdef CHECKGROUPORDER checkgrouporder(); #endif /* CHECKGROUPORDER */ return xfindgroup(active, name, activesize); } /* write active file, returns 0 for success, -1 for failure */ int writeactive(void) { FILE *a; struct newsgroup *g; mastr *c = mastr_new(PATH_MAX), *d; int err; size_t count = 0; mastr_vcat(c, spooldir, "/leaf.node/groupinfo.new", NULL); (void)unlink(mastr_str(c)); a = fopen(mastr_str(c), "w"); if (!a) { ln_log(LNLOG_SERR, LNLOG_CTOP, "cannot open new groupinfo file \"%s\": %m", mastr_str(c)); mastr_delete(c); return -1; } /* count members in array and sort it */ g = active; err = 0; if (g) { while (g->name) { count++; g++; } qsort(active, count, sizeof(struct newsgroup), &compactive); validateactive(); g = active; while ((err != EOF) && g->name) { if (strlen(g->name)) { err = fputs(g->name, a); if (err == EOF) break; if (fprintf(a, " %lu %lu %lu ", g->last, g->first, (unsigned long)g->age) < 0) { err = EOF; break; } if (err == EOF) break; err = fputs(g->desc && *(g->desc) ? g->desc : "-x-", a); if (err == EOF) break; err = fputs("\n", a); if (err == EOF) break; } g++; } } if (fflush(a) || fsync(fileno(a)) || fclose(a)) err = EOF; if (err == EOF) { ln_log(LNLOG_SERR, LNLOG_CTOP, "failed writing new groupinfo file \"%s\": %m", mastr_str(c)); unlink(mastr_str(c)); mastr_delete(c); return -1; } d = mastr_new(PATH_MAX); mastr_vcat(d, spooldir, "/leaf.node/groupinfo", NULL); if (rename(mastr_str(c), mastr_str(d))) { ln_log(LNLOG_SERR, LNLOG_CTOP, "failed to rename new groupinfo \"%s\" file into proper place \"%s\": %m", mastr_str(c), mastr_str(d)); unlink(mastr_str(c)); mastr_delete(d); mastr_delete(c); return -1; } else { if (verbose) printf("wrote active file with %lu line%s\n", (unsigned long)count, PLURAL(count)); syslog(LOG_INFO, "wrote active file with %lu line%s", (unsigned long)count, PLURAL(count)); } mastr_delete(d); mastr_delete(c); return 0; } /* * free active list. Written by Lloyd Zusman */ void freeactive(struct newsgroup *act) { struct newsgroup *g; if (act == NULL) return; g = act; while (g->name) { free(g->name); if (g->desc) free(g->desc); g++; } free(act); } /* * read active file into memory */ void readactive(void) { char *buf; /*@dependent@*/ char *p, *q, *r; unsigned long lu; off_t n; struct stat st; FILE *f; /*@dependent@*/ struct newsgroup *g; char s[SIZE_s+1]; if (active) { freeactive(active); active = NULL; } xsnprintf(s, SIZE_s, "%s/leaf.node/groupinfo", spooldir); if ((f = fopen(s, "r")) != NULL) { if (fstat(fileno(f), &st)) { syslog(LOG_ERR, "can't stat %s: %m", s); (void)fclose(f); return; } if (!S_ISREG(st.st_mode)) { syslog(LOG_ERR, "%s not a regular file", s); (void)fclose(f); return; } buf = critmalloc(st.st_size + 2, "Reading group info"); n = fread(buf, 1, st.st_size, f); if ((off_t) n < st.st_size) { syslog(LOG_ERR, "Groupinfo file truncated while reading: %ld < %ld.", (long)n, (long)st.st_size); } fclose(f); } else { int e = errno; syslog(LOG_ERR, "unable to open %s: %m", s); if (e == ENOENT) return; fprintf(stderr, "unable to open %s: %s, aborting", s, strerror(e)); exit(1); } n = ((off_t) n > st.st_size) ? st.st_size : (off_t) n; /* to read truncated groupinfo files correctly */ buf[n] = '\n'; buf[n + 1] = '\0'; /* 0-terminate string */ /* delete spurious 0-bytes */ while ((p = (char *)memchr(buf, '\0', st.st_size)) != NULL) *p = ' '; /* \n might be better, but produces more errors */ /* count lines = newsgroups */ activesize = 0; p = buf; while (p && *p && ((q = (char *)memchr(p, '\n', st.st_size)) != NULL)) { activesize++; p = q + 1; } active = (struct newsgroup *)critmalloc((1 + activesize) * sizeof(struct newsgroup), "allocating active"); g = active; p = buf; while (p && *p) { q = strchr(p, '\n'); if (q) { *q = '\0'; if (strlen(p) == 0) { p = q + 1; continue; /* skip blank lines */ } } r = strchr(p, ' '); if (!q || !r) { if (!q && r) *r = '\0'; else if (q && !r) *q = '\0'; else if (strlen(p) > 30) { q = p + 30; *q = '\0'; } syslog(LOG_ERR, "Groupinfo file possibly truncated or damaged: %s", p); break; } *r++ = '\0'; *q++ = '\0'; g->name = critstrdup(p, "readactive"); if (sscanf(r, "%lu %lu %lu", &g->last, &g->first, &lu) != 3) { syslog(LOG_ERR, "Groupinfo file possibly truncated or damaged: %s", p); break; } g->age = lu; if (g->first == 0) g->first = 1; /* pseudoarticle */ if (g->last == 0) g->last = 1; p = r; for (n = 0; n < 3; n++) { /* Skip the numbers */ p = strchr(r, ' '); r = p + 1; } if (strcmp(r, "-x-") == 0) g->desc = NULL; else g->desc = critstrdup(r, "readactive"); p = q; /* next record */ g++; } free(buf); /* last record, to mark end of array */ g->name = NULL; g->first = 0; g->last = 0; g->age = 0; g->desc = NULL; /* count member in the array - maybe there were some empty lines */ g = active; activesize = 0; while (g->name) { g++; activesize++; } /* sort the thing, just to be sure */ qsort(active, activesize, sizeof(struct newsgroup), &compactive); validateactive(); } /* * fake active file if it cannot be retrieved from the server */ void fakeactive(void) { DIR *d; struct dirent *de; DIR *ng; struct dirent *nga; long unsigned int i; long unsigned first, last; char *p; char s[SIZE_s+1]; struct stat st; time_t age; killactiveread(); /* force reading active file regardless */ xsnprintf(s, SIZE_s, "%s/interesting.groups", spooldir); d = opendir(s); if (!d) { syslog(LOG_ERR, "cannot open directory %s: %m", s); return; } while ((de = readdir(d))) { if (isalnum((unsigned char)*(de->d_name)) && chdirgroup(de->d_name, FALSE)) { /* get first and last article from the directory. This is * the most secure way to get to that information since the * .overview files may be corrupt as well * If the group doesn't exist, just ignore the active entry. */ first = ULONG_MAX; last = 0; ng = opendir("."); while ((nga = readdir(ng)) != NULL) { if (isdigit((unsigned char)*(nga->d_name))) { p = NULL; i = strtoul(nga->d_name, &p, 10); if (*p == '\0') { if (i < first) first = i; if (i > last) last = i; } } } if (first > last) { first = 1; last = 1; } closedir(ng); if (debugmode) syslog(LOG_DEBUG, "parsed directory %s: first %lu, last %lu", de->d_name, first, last); if (0 == stat(".", &st)) age = st.st_ctime; else age = 0; insertgroup(de->d_name, first, last, age); } } mergegroups(); closedir(d); } char * activeread(void) { const char *a = "/active.read"; size_t l; char *t = critmalloc((l = strlen(spooldir)) + strlen(a) + 1, "activeread"); strcpy(t, spooldir); /* RATS: ignore */ strcpy(t + l, a); /* RATS: ignore */ return t; } /* Set a mark that the active file needs to be refetched (as though * fetchnews -f had been used) next time, by removing active.read file */ int killactiveread(void) { int rc = 0; char *t = activeread(); if (unlink(t) && errno != ENOENT) { ln_log(LNLOG_SERR, LNLOG_CTOP, "cannot delete %s: %m", t); rc = -1; } free(t); return rc; }