/* $Id: ng.c 6494 2003-10-20 01:12:50Z rra $
**
** Routine for the in-core data structures for the active and newsfeeds
** files.
*/
#include "config.h"
#include "clibrary.h"
#include <dirent.h>
#include "inn/innconf.h"
#include "innd.h"
#include "ov.h"
/*
** Hash function taken from Chris Torek's hash package posted to
** comp.lang.c on 18-Oct-90 in <27038@mimsy.umd.edu>. Thanks, Chris.
*/
#define NGH_HASH(Name, p, j) \
for (p = Name, j = 0; *p; ) j = (j << 5) + j + *p++
/*
** Size of hash table. Change NGH_BUCKET if not a power of two.
*/
#define NGH_SIZE 2048
#define NGH_BUCKET(j) &NGHtable[j & (NGH_SIZE - 1)]
/*
** Newsgroup hash entry, which is really a hash bucket -- pointers
** to all the groups with this hash code.
*/
typedef struct _NGHASH {
int Size;
int Used;
NEWSGROUP **Groups;
} NGHASH;
static struct buffer NGnames;
static NGHASH NGHtable[NGH_SIZE];
static int NGHbuckets;
static int NGHcount;
/*
** Sorting predicate for qsort call in NGparsefile. Put newsgroups in
** rough order of their activity. Will be better if we write a "counts"
** file sometime.
*/
static int
NGcompare(const void *p1, const void *p2)
{
return ((const NEWSGROUP **)p1)[0]->Last -
((const NEWSGROUP **)p2)[0]->Last;
}
/*
** Parse a single line from the active file, filling in ngp. Be careful
** not to write NUL's into the in-core copy, since we're either mmap(2)'d,
** or we want to just blat it out to disk later.
*/
static bool
NGparseentry(NEWSGROUP *ngp, const char *p, char *end)
{
char *q;
unsigned int j;
NGHASH *htp;
NEWSGROUP **ngpp;
int i;
ARTNUM lo;
if ((q = strchr(p, ' ')) == NULL)
return false;
i = q - p;
ngp->NameLength = i;
ngp->Name = &NGnames.data[NGnames.used];
strncpy(ngp->Name, p, i);
ngp->Name[i] = '\0';
NGnames.used += i + 1;
ngp->LastString = ++q;
if ((q = strchr(q, ' ')) == NULL || q > end)
return false;
ngp->Lastwidth = q - ngp->LastString;
if ((q = strchr(q, ' ')) == NULL || q > end)
return false;
lo = (ARTNUM)atol(q + 1);
if ((q = strchr(q + 1, ' ')) == NULL || q > end)
return false;
ngp->Rest = ++q;
/* We count on atoi() to stop at the space after the digits! */
ngp->Last = atol(ngp->LastString);
ngp->nSites = 0;
ngp->Sites = xmalloc(NGHcount * sizeof(int));
ngp->nPoison = 0;
ngp->Poison = xmalloc(NGHcount * sizeof(int));
ngp->Alias = NULL;
/* Find the right bucket for the group, make sure there is room. */
NGH_HASH(ngp->Name, p, j);
htp = NGH_BUCKET(j);
for (p = ngp->Name, ngpp = htp->Groups, i = htp->Used; --i >= 0; ngpp++)
if (*p == ngpp[0]->Name[0] && strcmp(p, ngpp[0]->Name) == 0) {
syslog(L_ERROR, "%s duplicate_group %s", LogName, p);
return false;
}
if (htp->Used >= htp->Size) {
htp->Size += NGHbuckets;
htp->Groups = xrealloc(htp->Groups, htp->Size * sizeof(NEWSGROUP *));
}
htp->Groups[htp->Used++] = ngp;
if (innconf->enableoverview && !OVgroupadd(ngp->Name, lo, ngp->Last, ngp->Rest))
return false;
return true;
}
/*
** Parse the active file, building the initial Groups global.
*/
void
NGparsefile(void)
{
char *p;
char *q;
int i;
bool SawMe;
NEWSGROUP *ngp;
NGHASH *htp;
char **strings;
char *active;
char *end;
/* If re-reading, remove anything we might have had. */
NGclose();
/* Get active file and space for group entries. */
active = ICDreadactive(&end);
for (p = active, i = 0; p < end; p++)
if (*p == '\n') i++;
if ((nGroups = i) == 0) {
syslog(L_FATAL, "%s empty active file", LogName);
exit(1);
}
Groups = xmalloc(nGroups * sizeof(NEWSGROUP));
GroupPointers = xmalloc(nGroups * sizeof(NEWSGROUP *));
/* Get space to hold copies of the names. This might take more space
* than individually allocating each element, but it is definitely easier
* on the system. */
i = end - active;
NGnames.size = i;
NGnames.data = xmalloc(NGnames.size + 1);
NGnames.used = 0;
/* Set up the default hash buckets. */
NGHbuckets = nGroups / NGH_SIZE;
if (NGHbuckets == 0)
NGHbuckets = 1;
if (NGHtable[0].Groups)
for (i = NGH_SIZE, htp = NGHtable; --i >= 0; htp++)
htp->Used = 0;
else
for (i = NGH_SIZE, htp = NGHtable; --i >= 0; htp++) {
htp->Size = NGHbuckets;
htp->Groups = xmalloc(htp->Size * sizeof(NEWSGROUP *));
htp->Used = 0;
}
/* Count the number of sites. */
SawMe = false;
for (strings = SITEreadfile(true), i = 0; (p = strings[i]) != NULL; i++)
if (*p == 'M' && *++p == 'E' && *++p == ':')
SawMe = true;
if (i == 0 || (i == 1 && SawMe)) {
syslog(L_ERROR, "%s bad_newsfeeds no feeding sites", LogName);
NGHcount = 1;
}
else
NGHcount = i;
/* Loop over all lines in the active file, filling in the fields of
* the Groups array. */
for (p = active, ngp = Groups, i = nGroups; --i >= 0; ngp++, p = q + 1) {
ngp->Start = p - active;
if ((q = strchr(p, '\n')) == NULL || !NGparseentry(ngp, p, q)) {
syslog(L_FATAL, "%s bad_active %s...", LogName, MaxLength(p, q));
exit(1);
}
}
/* Sort each bucket. */
for (i = NGH_SIZE, htp = NGHtable; --i >= 0; htp++)
if (htp->Used > 1)
qsort(htp->Groups, htp->Used, sizeof htp->Groups[0], NGcompare);
/* Chase down any alias flags. */
for (ngp = Groups, i = nGroups; --i >= 0; ngp++)
if (ngp->Rest[0] == NF_FLAG_ALIAS) {
ngp->Alias = ngp;
if ((p = strchr(ngp->Alias->Rest, '\n')) != NULL)
*p = '\0';
ngp->Alias = NGfind(&ngp->Alias->Rest[1]);
if (p)
*p = '\n';
if (ngp->Alias != NULL && ngp->Alias->Rest[0] == NF_FLAG_ALIAS)
syslog(L_NOTICE, "%s alias_error %s too many levels",
LogName, ngp->Name);
}
}
/*
** Free allocated memory
*/
void
NGclose(void)
{
int i;
NEWSGROUP *ngp;
NGHASH *htp;
if (Groups) {
for (i = nGroups, ngp = Groups; --i >= 0; ngp++) {
free(ngp->Sites);
free(ngp->Poison);
}
free(Groups);
Groups = NULL;
free(GroupPointers);
free(NGnames.data);
}
for (i = NGH_SIZE, htp = NGHtable; --i >= 0; htp++) {
htp->Size = NGHbuckets;
if (htp->Groups) {
free(htp->Groups);
htp->Used = 0;
htp->Groups = NULL;
}
}
}
/*
** Hash a newsgroup and see if we get it.
*/
NEWSGROUP *
NGfind(const char *Name)
{
const char *p;
int i;
unsigned int j;
NEWSGROUP **ngp;
char c;
NGHASH *htp;
NGH_HASH(Name, p, j);
htp = NGH_BUCKET(j);
for (c = *Name, ngp = htp->Groups, i = htp->Used; --i >= 0; ngp++)
if (c == ngp[0]->Name[0] && strcmp(Name, ngp[0]->Name) == 0)
return ngp[0];
return NULL;
}
/*
** Split a newsgroups header line into the groups we get. Return the
** number of newsgroups. ' ' and '\t' are dropped when copying.
*/
int
NGsplit(char *p, int size, LISTBUFFER *list)
{
char **gp, *q;
int i;
/* setup buffer */
SetupListBuffer(size, list);
/* loop over and copy */
for (i = 0, q = list->Data, gp = list->List ; *p ; p++, *q++ = '\0') {
/* skip leading separators and white spaces. */
for (; *p && (NG_ISSEP(*p) || ISWHITE(*p)) ; p++)
continue;
if (*p == '\0')
break;
if (i == list->ListLength) {
list->ListLength += DEFAULTNGBOXSIZE;
list->List = xrealloc(list->List, list->ListLength * sizeof(char *));
gp = &list->List[i];
}
/* mark the start of the newsgroup, move to the end of it while copying */
for (*gp++ = q, i++ ; *p && !NG_ISSEP(*p) && !ISWHITE(*p) ;) {
if (*p == ':')
/* reject if ':' is included */
return 0;
*q++ = *p++;
continue;
}
if (*p == '\0')
break;
}
*q = '\0';
if (i == list->ListLength) {
list->ListLength += DEFAULTNGBOXSIZE;
list->List = xrealloc(list->List, list->ListLength * sizeof(char *));
gp = &list->List[i];
}
*gp = NULL;
return i;
}
/*
** Renumber a group.
*/
static char NORENUMBER[] = "%s cant renumber %s %s too wide";
static char RENUMBER[] = "%s renumber %s %s from %ld to %ld";
bool
NGrenumber(NEWSGROUP *ngp)
{
int low, high, count,flag;
char *f2;
char *f3;
char *f4;
char *start;
long lomark, himark;
long l;
char *dummy;
if (!innconf->enableoverview) return true; /* can't do anything w/o overview */
/* Get a valid offset into the active file. */
if (ICDneedsetup) {
syslog(L_ERROR, "%s unsynched must reload before renumber", LogName);
return false;
}
start = ICDreadactive(&dummy) + ngp->Start;
/* Check the file format. */
if ((f2 = strchr(start, ' ')) == NULL
|| (f3 = strchr(++f2, ' ')) == NULL
|| (f4 = strchr(++f3, ' ')) == NULL) {
syslog(L_ERROR, "%s bad_format active %s",
LogName, MaxLength(start, start));
return false;
}
himark = atol(f2);
lomark = himark + 1;
/* note these will be the low and himarks if the group turns out to be empty. */
/* Check overview data for the group. */
if (!OVgroupstats(ngp->Name, &low, &high, &count, &flag)) return false;
if (count != 0) {
/* non-empty group, so set low/himarks from overview. */
lomark = low;
himark = high;
}
l = atol(f2);
if (himark > l) {
syslog(L_NOTICE, RENUMBER, LogName, ngp->Name, "hi", l, himark);
if (!FormatLong(f2, himark, f3 - f2 - 1)) {
syslog(L_ERROR, NORENUMBER, LogName, ngp->Name, "hi");
return false;
}
ngp->Last = himark;
ICDactivedirty++;
} else if (himark < l) {
syslog(L_NOTICE, "%s renumber %s hi not decreasing %ld to %ld",
LogName, ngp->Name, l, himark);
}
l = atol(f3);
if (lomark != l) {
if (lomark < l)
syslog(L_NOTICE, RENUMBER, LogName, ngp->Name, "lo", l, lomark);
if (!FormatLong(f3, lomark, f4 - f3)) {
syslog(L_ERROR, NORENUMBER, LogName, ngp->Name, "lo");
return false;
}
ICDactivedirty++;
}
return true;
}
/*
* Set the low article count for the given group.
* Like NGrenumber(), but we don't scan the spool,
* and the himark is ignored.
*/
bool
NGlowmark(NEWSGROUP *ngp, long lomark)
{
long l;
char *f2, *f3, *f4;
char *start;
start = ICDreadactive(&f2) + ngp->Start;
/* Check the file format. */
if ((f2 = strchr(start, ' ')) == NULL
|| (f3 = strchr(++f2, ' ')) == NULL
|| (f4 = strchr(++f3, ' ')) == NULL) {
syslog(L_ERROR, "%s bad_format active %s",
LogName, MaxLength(start, start));
return false;
}
l = atol(f3);
if (lomark != l) {
if (lomark < l)
syslog(L_NOTICE, RENUMBER, LogName, ngp->Name, "lo", l, lomark);
if (!FormatLong(f3, lomark, f4 - f3)) {
syslog(L_ERROR, NORENUMBER, LogName, ngp->Name, "lo");
return false;
}
ICDactivedirty++;
}
return true;
}
syntax highlighted by Code2HTML, v. 0.9.1