1 /* $Id: ng.c 6494 2003-10-20 01:12:50Z rra $
3 ** Routine for the in-core data structures for the active and newsfeeds
11 #include "inn/innconf.h"
17 ** Hash function taken from Chris Torek's hash package posted to
18 ** comp.lang.c on 18-Oct-90 in <27038@mimsy.umd.edu>. Thanks, Chris.
20 #define NGH_HASH(Name, p, j) \
21 for (p = Name, j = 0; *p; ) j = (j << 5) + j + *p++
25 ** Size of hash table. Change NGH_BUCKET if not a power of two.
28 #define NGH_BUCKET(j) &NGHtable[j & (NGH_SIZE - 1)]
32 ** Newsgroup hash entry, which is really a hash bucket -- pointers
33 ** to all the groups with this hash code.
35 typedef struct _NGHASH {
42 static struct buffer NGnames;
43 static NGHASH NGHtable[NGH_SIZE];
44 static int NGHbuckets;
50 ** Sorting predicate for qsort call in NGparsefile. Put newsgroups in
51 ** rough order of their activity. Will be better if we write a "counts"
55 NGcompare(const void *p1, const void *p2)
57 return ((const NEWSGROUP **)p1)[0]->Last -
58 ((const NEWSGROUP **)p2)[0]->Last;
63 ** Parse a single line from the active file, filling in ngp. Be careful
64 ** not to write NUL's into the in-core copy, since we're either mmap(2)'d,
65 ** or we want to just blat it out to disk later.
68 NGparseentry(NEWSGROUP *ngp, const char *p, char *end)
77 if ((q = strchr(p, ' ')) == NULL)
82 ngp->Name = &NGnames.data[NGnames.used];
83 strncpy(ngp->Name, p, i);
85 NGnames.used += i + 1;
87 ngp->LastString = ++q;
88 if ((q = strchr(q, ' ')) == NULL || q > end)
90 ngp->Lastwidth = q - ngp->LastString;
91 if ((q = strchr(q, ' ')) == NULL || q > end)
93 lo = (ARTNUM)atol(q + 1);
94 if ((q = strchr(q + 1, ' ')) == NULL || q > end)
97 /* We count on atoi() to stop at the space after the digits! */
98 ngp->Last = atol(ngp->LastString);
100 ngp->Sites = xmalloc(NGHcount * sizeof(int));
102 ngp->Poison = xmalloc(NGHcount * sizeof(int));
105 /* Find the right bucket for the group, make sure there is room. */
106 NGH_HASH(ngp->Name, p, j);
108 for (p = ngp->Name, ngpp = htp->Groups, i = htp->Used; --i >= 0; ngpp++)
109 if (*p == ngpp[0]->Name[0] && strcmp(p, ngpp[0]->Name) == 0) {
110 syslog(L_ERROR, "%s duplicate_group %s", LogName, p);
113 if (htp->Used >= htp->Size) {
114 htp->Size += NGHbuckets;
115 htp->Groups = xrealloc(htp->Groups, htp->Size * sizeof(NEWSGROUP *));
117 htp->Groups[htp->Used++] = ngp;
119 if (innconf->enableoverview && !OVgroupadd(ngp->Name, lo, ngp->Last, ngp->Rest))
127 ** Parse the active file, building the initial Groups global.
142 /* If re-reading, remove anything we might have had. */
145 /* Get active file and space for group entries. */
146 active = ICDreadactive(&end);
147 for (p = active, i = 0; p < end; p++)
149 if ((nGroups = i) == 0) {
150 syslog(L_FATAL, "%s empty active file", LogName);
153 Groups = xmalloc(nGroups * sizeof(NEWSGROUP));
154 GroupPointers = xmalloc(nGroups * sizeof(NEWSGROUP *));
156 /* Get space to hold copies of the names. This might take more space
157 * than individually allocating each element, but it is definitely easier
161 NGnames.data = xmalloc(NGnames.size + 1);
164 /* Set up the default hash buckets. */
165 NGHbuckets = nGroups / NGH_SIZE;
168 if (NGHtable[0].Groups)
169 for (i = NGH_SIZE, htp = NGHtable; --i >= 0; htp++)
172 for (i = NGH_SIZE, htp = NGHtable; --i >= 0; htp++) {
173 htp->Size = NGHbuckets;
174 htp->Groups = xmalloc(htp->Size * sizeof(NEWSGROUP *));
178 /* Count the number of sites. */
180 for (strings = SITEreadfile(true), i = 0; (p = strings[i]) != NULL; i++)
181 if (*p == 'M' && *++p == 'E' && *++p == ':')
183 if (i == 0 || (i == 1 && SawMe)) {
184 syslog(L_ERROR, "%s bad_newsfeeds no feeding sites", LogName);
190 /* Loop over all lines in the active file, filling in the fields of
191 * the Groups array. */
192 for (p = active, ngp = Groups, i = nGroups; --i >= 0; ngp++, p = q + 1) {
193 ngp->Start = p - active;
194 if ((q = strchr(p, '\n')) == NULL || !NGparseentry(ngp, p, q)) {
195 syslog(L_FATAL, "%s bad_active %s...", LogName, MaxLength(p, q));
200 /* Sort each bucket. */
201 for (i = NGH_SIZE, htp = NGHtable; --i >= 0; htp++)
203 qsort(htp->Groups, htp->Used, sizeof htp->Groups[0], NGcompare);
205 /* Chase down any alias flags. */
206 for (ngp = Groups, i = nGroups; --i >= 0; ngp++)
207 if (ngp->Rest[0] == NF_FLAG_ALIAS) {
209 if ((p = strchr(ngp->Alias->Rest, '\n')) != NULL)
211 ngp->Alias = NGfind(&ngp->Alias->Rest[1]);
214 if (ngp->Alias != NULL && ngp->Alias->Rest[0] == NF_FLAG_ALIAS)
215 syslog(L_NOTICE, "%s alias_error %s too many levels",
221 ** Free allocated memory
231 for (i = nGroups, ngp = Groups; --i >= 0; ngp++) {
241 for (i = NGH_SIZE, htp = NGHtable; --i >= 0; htp++) {
242 htp->Size = NGHbuckets;
252 ** Hash a newsgroup and see if we get it.
255 NGfind(const char *Name)
264 NGH_HASH(Name, p, j);
266 for (c = *Name, ngp = htp->Groups, i = htp->Used; --i >= 0; ngp++)
267 if (c == ngp[0]->Name[0] && strcmp(Name, ngp[0]->Name) == 0)
274 ** Split a newsgroups header line into the groups we get. Return the
275 ** number of newsgroups. ' ' and '\t' are dropped when copying.
278 NGsplit(char *p, int size, LISTBUFFER *list)
284 SetupListBuffer(size, list);
286 /* loop over and copy */
287 for (i = 0, q = list->Data, gp = list->List ; *p ; p++, *q++ = '\0') {
288 /* skip leading separators and white spaces. */
289 for (; *p && (NG_ISSEP(*p) || ISWHITE(*p)) ; p++)
294 if (i == list->ListLength) {
295 list->ListLength += DEFAULTNGBOXSIZE;
296 list->List = xrealloc(list->List, list->ListLength * sizeof(char *));
299 /* mark the start of the newsgroup, move to the end of it while copying */
300 for (*gp++ = q, i++ ; *p && !NG_ISSEP(*p) && !ISWHITE(*p) ;) {
302 /* reject if ':' is included */
311 if (i == list->ListLength) {
312 list->ListLength += DEFAULTNGBOXSIZE;
313 list->List = xrealloc(list->List, list->ListLength * sizeof(char *));
324 static char NORENUMBER[] = "%s cant renumber %s %s too wide";
325 static char RENUMBER[] = "%s renumber %s %s from %ld to %ld";
328 NGrenumber(NEWSGROUP *ngp)
330 int low, high, count,flag;
339 if (!innconf->enableoverview) return true; /* can't do anything w/o overview */
341 /* Get a valid offset into the active file. */
343 syslog(L_ERROR, "%s unsynched must reload before renumber", LogName);
346 start = ICDreadactive(&dummy) + ngp->Start;
348 /* Check the file format. */
349 if ((f2 = strchr(start, ' ')) == NULL
350 || (f3 = strchr(++f2, ' ')) == NULL
351 || (f4 = strchr(++f3, ' ')) == NULL) {
352 syslog(L_ERROR, "%s bad_format active %s",
353 LogName, MaxLength(start, start));
358 /* note these will be the low and himarks if the group turns out to be empty. */
360 /* Check overview data for the group. */
361 if (!OVgroupstats(ngp->Name, &low, &high, &count, &flag)) return false;
363 /* non-empty group, so set low/himarks from overview. */
369 syslog(L_NOTICE, RENUMBER, LogName, ngp->Name, "hi", l, himark);
370 if (!FormatLong(f2, himark, f3 - f2 - 1)) {
371 syslog(L_ERROR, NORENUMBER, LogName, ngp->Name, "hi");
376 } else if (himark < l) {
377 syslog(L_NOTICE, "%s renumber %s hi not decreasing %ld to %ld",
378 LogName, ngp->Name, l, himark);
383 syslog(L_NOTICE, RENUMBER, LogName, ngp->Name, "lo", l, lomark);
384 if (!FormatLong(f3, lomark, f4 - f3)) {
385 syslog(L_ERROR, NORENUMBER, LogName, ngp->Name, "lo");
394 * Set the low article count for the given group.
395 * Like NGrenumber(), but we don't scan the spool,
396 * and the himark is ignored.
399 NGlowmark(NEWSGROUP *ngp, long lomark)
405 start = ICDreadactive(&f2) + ngp->Start;
406 /* Check the file format. */
407 if ((f2 = strchr(start, ' ')) == NULL
408 || (f3 = strchr(++f2, ' ')) == NULL
409 || (f4 = strchr(++f3, ' ')) == NULL) {
410 syslog(L_ERROR, "%s bad_format active %s",
411 LogName, MaxLength(start, start));
417 syslog(L_NOTICE, RENUMBER, LogName, ngp->Name, "lo", l, lomark);
418 if (!FormatLong(f3, lomark, f4 - f3)) {
419 syslog(L_ERROR, NORENUMBER, LogName, ngp->Name, "lo");