Logo Search packages:      
Sourcecode: leafnode version File versions

activutil.c

/*
 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@web.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;
}

Generated by  Doxygen 1.6.0   Back to index