/*
libutil -- deal with active file
Written by Cornelius Krasel <krasel@wpxx02.toxi.uni-wuerzburg.de>.
Copyright 2000.
Reused some old code written by Arnt Gulbrandsen <agulbra@troll.no>,
copyright 1995, modified by (amongst others) Cornelius Krasel
<krasel@wpxx02.toxi.uni-wuerzburg.de>, Randolf Skerka
<Randolf.Skerka@gmx.de>, Kent Robotti <robotti@erols.com> and
Markus Enzenberger <enz@cip.physik.uni-muenchen.de>. Copyright
for the modifications 1997-1999.
Modified and copyright of the modifications by:
2001 - 2002 Matthias Andree <matthias.andree@gmx.de>
2002 Ralf Wildenhues <ralf.wildenhues@gmx.de>
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 <ctype.h>
#include "system.h"
#include <limits.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#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;
}
syntax highlighted by Code2HTML, v. 0.9.1