chiark / gitweb /
use libinn logging where applicable - compiles
[inn-innduct.git] / innd / ng.c
1 /*  $Id: ng.c 6494 2003-10-20 01:12:50Z rra $
2 **
3 **  Routine for the in-core data structures for the active and newsfeeds
4 **  files.
5 */
6
7 #include "config.h"
8 #include "clibrary.h"
9 #include <dirent.h>
10
11 #include "inn/innconf.h"
12 #include "innd.h"
13 #include "ov.h"
14
15
16 /*
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.
19 */
20 #define NGH_HASH(Name, p, j)    \
21         for (p = Name, j = 0; *p; ) j = (j << 5) + j + *p++
22
23
24 /*
25 **  Size of hash table.   Change NGH_BUCKET if not a power of two.
26 */
27 #define NGH_SIZE        2048
28 #define NGH_BUCKET(j)   &NGHtable[j & (NGH_SIZE - 1)]
29
30
31 /*
32 **  Newsgroup hash entry, which is really a hash bucket -- pointers
33 **  to all the groups with this hash code.
34 */
35 typedef struct _NGHASH {
36     int                 Size;
37     int                 Used;
38     NEWSGROUP           **Groups;
39 } NGHASH;
40
41
42 static struct buffer    NGnames;
43 static NGHASH           NGHtable[NGH_SIZE];
44 static int              NGHbuckets;
45 static int              NGHcount;
46
47 \f
48
49 /*
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"
52 **  file sometime.
53 */
54 static int
55 NGcompare(const void *p1, const void *p2)
56 {
57     return ((const NEWSGROUP **)p1)[0]->Last -  
58            ((const NEWSGROUP **)p2)[0]->Last;
59 }
60
61
62 /*
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.
66 */
67 static bool
68 NGparseentry(NEWSGROUP *ngp, const char *p, char *end)
69 {
70     char                *q;
71     unsigned int        j;
72     NGHASH              *htp;
73     NEWSGROUP           **ngpp;
74     int                 i;
75     ARTNUM              lo;
76
77     if ((q = strchr(p, ' ')) == NULL)
78         return false;
79     i = q - p;
80
81     ngp->NameLength = i;
82     ngp->Name = &NGnames.data[NGnames.used];
83     strncpy(ngp->Name, p, i);
84     ngp->Name[i] = '\0';
85     NGnames.used += i + 1;
86
87     ngp->LastString = ++q;
88     if ((q = strchr(q, ' ')) == NULL || q > end)
89         return false;
90     ngp->Lastwidth = q - ngp->LastString;
91     if ((q = strchr(q, ' ')) == NULL || q > end)
92         return false;
93     lo = (ARTNUM)atol(q + 1);
94     if ((q = strchr(q + 1, ' ')) == NULL || q > end)
95         return false;
96     ngp->Rest = ++q;
97     /* We count on atoi() to stop at the space after the digits! */
98     ngp->Last = atol(ngp->LastString);
99     ngp->nSites = 0;
100     ngp->Sites = xmalloc(NGHcount * sizeof(int));
101     ngp->nPoison = 0;
102     ngp->Poison = xmalloc(NGHcount * sizeof(int));
103     ngp->Alias = NULL;
104
105     /* Find the right bucket for the group, make sure there is room. */
106     NGH_HASH(ngp->Name, p, j);
107     htp = NGH_BUCKET(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);
111             return false;
112         }
113     if (htp->Used >= htp->Size) {
114         htp->Size += NGHbuckets;
115         htp->Groups = xrealloc(htp->Groups, htp->Size * sizeof(NEWSGROUP *));
116     }
117     htp->Groups[htp->Used++] = ngp;
118
119     if (innconf->enableoverview && !OVgroupadd(ngp->Name, lo, ngp->Last, ngp->Rest))
120         return false;
121
122     return true;
123 }
124
125
126 /*
127 **  Parse the active file, building the initial Groups global.
128 */
129 void
130 NGparsefile(void)
131 {
132     char        *p;
133     char        *q;
134     int         i;
135     bool        SawMe;
136     NEWSGROUP   *ngp;
137     NGHASH      *htp;
138     char        **strings;
139     char        *active;
140     char        *end;
141
142     /* If re-reading, remove anything we might have had. */
143     NGclose();
144
145     /* Get active file and space for group entries. */
146     active = ICDreadactive(&end);
147     for (p = active, i = 0; p < end; p++)
148         if (*p == '\n') i++;
149     if ((nGroups = i) == 0) {
150         syslog(L_FATAL, "%s empty active file", LogName);
151         exit(1);
152     }
153     Groups = xmalloc(nGroups * sizeof(NEWSGROUP));
154     GroupPointers = xmalloc(nGroups * sizeof(NEWSGROUP *));
155
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
158      * on the system. */
159     i = end - active;
160     NGnames.size = i;
161     NGnames.data = xmalloc(NGnames.size + 1);
162     NGnames.used = 0;
163
164     /* Set up the default hash buckets. */
165     NGHbuckets = nGroups / NGH_SIZE;
166     if (NGHbuckets == 0)
167         NGHbuckets = 1;
168     if (NGHtable[0].Groups)
169         for (i = NGH_SIZE, htp = NGHtable; --i >= 0; htp++)
170             htp->Used = 0;
171     else
172         for (i = NGH_SIZE, htp = NGHtable; --i >= 0; htp++) {
173             htp->Size = NGHbuckets;
174             htp->Groups = xmalloc(htp->Size * sizeof(NEWSGROUP *));
175             htp->Used = 0;
176         }
177
178     /* Count the number of sites. */
179     SawMe = false;
180     for (strings = SITEreadfile(true), i = 0; (p = strings[i]) != NULL; i++)
181         if (*p == 'M' && *++p == 'E' && *++p == ':')
182             SawMe = true;
183     if (i == 0 || (i == 1 && SawMe)) {
184         syslog(L_ERROR, "%s bad_newsfeeds no feeding sites", LogName);
185         NGHcount = 1;
186     }
187     else
188         NGHcount = i;
189
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));
196             exit(1);
197         }
198     }
199
200     /* Sort each bucket. */
201     for (i = NGH_SIZE, htp = NGHtable; --i >= 0; htp++)
202         if (htp->Used > 1)
203             qsort(htp->Groups, htp->Used, sizeof htp->Groups[0], NGcompare);
204
205     /* Chase down any alias flags. */
206     for (ngp = Groups, i = nGroups; --i >= 0; ngp++)
207         if (ngp->Rest[0] == NF_FLAG_ALIAS) {
208             ngp->Alias = ngp;
209             if ((p = strchr(ngp->Alias->Rest, '\n')) != NULL)
210                 *p = '\0';
211             ngp->Alias = NGfind(&ngp->Alias->Rest[1]);
212             if (p)
213                 *p = '\n';
214             if (ngp->Alias != NULL && ngp->Alias->Rest[0] == NF_FLAG_ALIAS)
215                 syslog(L_NOTICE, "%s alias_error %s too many levels",
216                     LogName, ngp->Name);
217         }
218 }
219
220 /*
221 ** Free allocated memory
222 */
223 void
224 NGclose(void)
225 {
226     int         i;
227     NEWSGROUP   *ngp;
228     NGHASH      *htp;
229
230     if (Groups) {
231         for (i = nGroups, ngp = Groups; --i >= 0; ngp++) {
232             free(ngp->Sites);
233             free(ngp->Poison);
234         }
235         free(Groups);
236         Groups = NULL;
237         free(GroupPointers);
238         free(NGnames.data);
239     }
240
241     for (i = NGH_SIZE, htp = NGHtable; --i >= 0; htp++) {
242       htp->Size = NGHbuckets;
243       if (htp->Groups) {
244         free(htp->Groups);
245         htp->Used = 0;
246         htp->Groups = NULL;
247       }
248     }
249 }
250
251 /*
252 **  Hash a newsgroup and see if we get it.
253 */
254 NEWSGROUP *
255 NGfind(const char *Name)
256 {
257     const char          *p;
258     int                 i;
259     unsigned int        j;
260     NEWSGROUP           **ngp;
261     char                c;
262     NGHASH              *htp;
263
264     NGH_HASH(Name, p, j);
265     htp = NGH_BUCKET(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)
268             return ngp[0];
269     return NULL;
270 }
271
272
273 /*
274 **  Split a newsgroups header line into the groups we get.  Return the
275 **  number of newsgroups.  ' ' and '\t' are dropped when copying.
276 */
277 int
278 NGsplit(char *p, int size, LISTBUFFER *list)
279 {
280   char          **gp, *q;
281   int           i;
282
283   /* setup buffer */
284   SetupListBuffer(size, list);
285
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++)
290       continue;
291     if (*p == '\0')
292       break;
293
294     if (i == list->ListLength) {
295       list->ListLength += DEFAULTNGBOXSIZE;
296       list->List = xrealloc(list->List, list->ListLength * sizeof(char *));
297       gp = &list->List[i];
298     }
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) ;) {
301       if (*p == ':')
302         /* reject if ':' is included */
303         return 0;
304       *q++ = *p++;
305       continue;
306     }
307     if (*p == '\0')
308       break;
309   }
310   *q = '\0';
311   if (i == list->ListLength) {
312     list->ListLength += DEFAULTNGBOXSIZE;
313     list->List = xrealloc(list->List, list->ListLength * sizeof(char *));
314     gp = &list->List[i];
315   }
316   *gp = NULL;
317   return i;
318 }
319
320
321 /*
322 **  Renumber a group.
323 */
324 static char             NORENUMBER[] = "%s cant renumber %s %s too wide";
325 static char             RENUMBER[] = "%s renumber %s %s from %ld to %ld";
326
327 bool
328 NGrenumber(NEWSGROUP *ngp)
329 {
330     int                 low, high, count,flag;
331     char                *f2;
332     char                *f3;
333     char                *f4;
334     char                *start;
335     long                lomark, himark;
336     long                l;
337     char                *dummy;
338
339     if (!innconf->enableoverview) return true; /* can't do anything w/o overview */
340
341     /* Get a valid offset into the active file. */
342     if (ICDneedsetup) {
343         syslog(L_ERROR, "%s unsynched must reload before renumber", LogName);
344         return false;
345     }
346     start = ICDreadactive(&dummy) + ngp->Start;
347
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));
354         return false;
355     }
356     himark = atol(f2);
357     lomark = himark + 1;
358     /* note these will be the low and himarks if the group turns out to be empty. */
359
360     /* Check overview data for the group. */
361     if (!OVgroupstats(ngp->Name, &low, &high, &count, &flag)) return false;
362     if (count != 0) {
363         /* non-empty group, so set low/himarks from overview. */
364         lomark = low;
365         himark = high;
366     }
367     l = atol(f2);
368     if (himark > l) {
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");
372             return false;
373         }
374         ngp->Last = himark;
375         ICDactivedirty++;
376     } else if (himark < l) {
377         syslog(L_NOTICE, "%s renumber %s hi not decreasing %ld to %ld",
378                LogName, ngp->Name, l, himark);
379     }
380     l = atol(f3);
381     if (lomark != l) {
382         if (lomark < l)
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");
386             return false;
387         }
388         ICDactivedirty++;
389     }
390     return true;
391 }
392
393 /*
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.
397  */
398 bool
399 NGlowmark(NEWSGROUP *ngp, long lomark)
400 {
401     long l;
402     char *f2, *f3, *f4;
403     char *start;
404
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));
412         return false;
413     }
414     l = atol(f3);
415     if (lomark != l) {
416         if (lomark < l)
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");
420             return false;
421         }
422         ICDactivedirty++;
423     }
424     return true;
425 }