chiark / gitweb /
Fix C%d[%d] messages
[inn-innduct.git] / expire / makehistory.c
1 /*  $Id: makehistory.c 7468 2005-12-12 03:23:21Z eagle $
2 **
3 **  Rebuild history/overview databases.
4 */
5
6 #include "config.h"
7 #include "clibrary.h"
8 #include "portable/wait.h"
9 #include <assert.h>
10 #include <errno.h>
11 #include <pwd.h>
12 #include <syslog.h>  
13
14 #include "inn/buffer.h"
15 #include "inn/history.h"
16 #include "inn/innconf.h"
17 #include "inn/messages.h"
18 #include "inn/qio.h"
19 #include "inn/wire.h"
20 #include "libinn.h"
21 #include "ov.h"
22 #include "paths.h"
23 #include "storage.h"
24
25 static const char usage[] = "\
26 Usage: makehistory [-bOIax] [-f file] [-l count] [-s size] [-T tmpdir]\n\
27 \n\
28     -b          delete bad articles from spool\n\
29     -e          read entire articles to compute proper byte count\n\
30     -f          write history entries to file (default $pathdb/history)\n\
31     -s size     size new history database for approximately size entries\n\
32     -a          open output history file in append mode\n\
33     -O          create overview entries for articles\n\
34     -I          do not create overview for articles numbered below lowmark\n\
35     -l count    size of overview updates (default 10000)\n\
36     -x          don't write history entries\n\
37     -T tmpdir   use directory tmpdir for temporary files\n\
38     -F          fork when writing overview\n";
39
40
41 /*
42 **  Information about the schema of the news overview files.
43 */
44 typedef struct _ARTOVERFIELD {
45     char        *Headername;
46     int         HeadernameLength;
47     bool        NeedHeadername;
48     const char  *Header;
49     int         HeaderLength;
50     bool        HasHeader;
51 } ARTOVERFIELD;
52
53 #define DEFAULT_SEGSIZE 10000;
54
55 bool NukeBadArts;
56 char *SchemaPath = NULL;
57 char *ActivePath = NULL;
58 char *HistoryPath = NULL;
59 struct history *History;
60 FILE *Overchan;
61 bool DoOverview;
62 bool Fork;
63 bool Cutofflow = false;
64 char *TmpDir;
65 int OverTmpSegSize, OverTmpSegCount;
66 FILE *OverTmpFile;
67 char *OverTmpPath = NULL;
68 bool NoHistory;
69 OVSORTTYPE sorttype;
70 int RetrMode;
71
72 TIMEINFO Now;
73
74 /* Misc variables needed for the overview creation code. */
75 static char             MESSAGEID[] = "Message-ID";
76 static char             EXPIRES[] = "Expires";
77 static char             DATE[] = "Date";
78 static char             XREF[] = "Xref";
79 static ARTOVERFIELD     *ARTfields; /* overview fields listed in overview.fmt */
80 static size_t           ARTfieldsize;
81 static ARTOVERFIELD     *Datep = (ARTOVERFIELD *)NULL;
82 static ARTOVERFIELD     *Msgidp = (ARTOVERFIELD *)NULL;
83 static ARTOVERFIELD     *Expp = (ARTOVERFIELD *)NULL;
84 static ARTOVERFIELD     *Xrefp = (ARTOVERFIELD *)NULL;
85 static ARTOVERFIELD     *Missfields; /* header fields not listed in 
86                                         overview.fmt, but ones that we need
87                                         (e.g. message-id */
88 static size_t           Missfieldsize = 0;
89
90 static void OverAddAllNewsgroups(void);
91
92 /*
93 **  Check and parse an date header line.  Return the new value or
94 **  zero on error.
95 */
96 static long
97 GetaDate(char *p)
98 {
99     time_t              t;
100
101     while (ISWHITE(*p))
102         p++;
103     if ((t = parsedate(p, &Now)) == -1)
104         return 0L;
105     return (long)t;
106 }
107
108 /*
109 **  Check and parse a Message-ID header line.  Return private space.
110 */
111 static const char *
112 GetMessageID(char *p)
113 {
114     static struct buffer buffer = { 0, 0, 0, NULL };
115
116     while (ISWHITE(*p))
117         p++;
118     if (p[0] != '<' || p[strlen(p) - 1] != '>')
119         return "";
120
121     /* Copy into re-used memory space, including NUL. */
122     buffer_set(&buffer, p, strlen(p)+1);
123     return buffer.data;
124 }
125
126 /*
127  * The overview temp file is used to accumulate overview lines as articles are
128  * scanned.  The format is
129  * (1st) newsgroup name\tToken\toverview data.
130  * When about 10000 lines of this overview data are accumulated, the data
131  * file is sorted and then read back in and the data added to overview.
132  * The sorting/batching helps improve efficiency.
133  */
134
135 /*
136  * Flush the unwritten OverTempFile data to disk, sort the file, read it 
137  * back in, and add it to overview. 
138  */
139
140 static void
141 FlushOverTmpFile(void)
142 {
143     char temp[SMBUF];
144     char *SortedTmpPath;
145     int i, pid, fd;
146     TOKEN token;
147     QIOSTATE *qp;
148     int count;
149     char *line, *p;
150     char *q = NULL;
151     char *r = NULL;
152     time_t arrived, expires;
153     static int first = 1;
154
155     if (OverTmpFile == NULL)
156         return;
157     if (fflush(OverTmpFile) == EOF || ferror(OverTmpFile) || fclose(OverTmpFile) == EOF)
158         sysdie("cannot close temporary overview file");
159     if(Fork) {
160         if(!first) { /* if previous one is running, wait for it */
161             int status;
162             wait(&status);
163             if((WIFEXITED(status) && WEXITSTATUS(status) != 0)
164                     || WIFSIGNALED(status))
165                 exit(1);
166         }
167
168         pid = fork();
169         if(pid == -1)
170             sysdie("cannot fork");
171         if(pid > 0) {
172             /* parent */
173             first = 0;
174             free(OverTmpPath);
175             OverTmpPath = NULL;
176             return;
177         }
178
179         /* child */
180         /* init the overview setup. */
181         if (!OVopen(OV_WRITE)) {
182             warn("cannot open overview");
183             _exit(1);
184         }
185         if (!OVctl(OVSORT, (void *)&sorttype)) {
186             warn("cannot obtain overview sorting information");
187             OVclose();
188             _exit(1);
189         }
190         if (!OVctl(OVCUTOFFLOW, (void *)&Cutofflow)) {
191             warn("cannot obtain overview cutoff information");
192             OVclose();
193             _exit(1);
194         }
195     }
196
197     /* This is a bit odd, but as long as other user's files can't be deleted
198        out of the temporary directory, it should work.  We're using mkstemp to
199        create a file and then passing its name to sort, which will then open
200        it again and overwrite it. */
201     SortedTmpPath = concatpath(TmpDir, "hisTXXXXXX");
202     fd = mkstemp(SortedTmpPath);
203     if (fd < 0) {
204         syswarn("cannot create temporary file");
205         OVclose();
206         Fork ? _exit(1) : exit(1);
207     }
208     close(fd);
209     snprintf(temp, sizeof(temp), "exec %s -T %s -t'%c' -o %s %s", _PATH_SORT,
210              TmpDir, '\t', SortedTmpPath, OverTmpPath);
211     
212     i = system(temp) >> 8;
213     if (i != 0) {
214         syswarn("cannot sort temporary overview file (%s exited %d)",
215                 _PATH_SORT, i);
216         OVclose();
217         Fork ? _exit(1) : exit(1);
218     }
219
220     /* don't need old path anymore. */
221     unlink(OverTmpPath);
222     free(OverTmpPath);
223     OverTmpPath = NULL;
224
225     /* read sorted lines. */
226     if ((qp = QIOopen(SortedTmpPath)) == NULL) {
227         syswarn("cannot open sorted overview file %s", SortedTmpPath);
228         OVclose();
229         Fork ? _exit(1) : exit(1);
230     }
231
232     for (count = 1; ; ++count) {
233         line = QIOread(qp);
234         if (line == NULL) {
235             if (QIOtoolong(qp)) {
236                 warn("overview line %d is too long", count);
237                 continue;
238             } else
239                 break;
240         }
241         if ((p = strchr(line, '\t')) == NULL 
242             || (q = strchr(p+1, '\t')) == NULL
243             || (r = strchr(q+1, '\t')) == NULL) {
244             warn("sorted overview file %s has a bad line at %d",
245                  SortedTmpPath, count);
246             continue;
247         }
248         /* p+1 now points to start of token, q+1 points to start of overline. */
249         if (sorttype == OVNEWSGROUP) {
250             *p++ = '\0';
251             *q++ = '\0';
252             *r++ = '\0';
253             arrived = (time_t)atol(p);
254             expires = (time_t)atol(q);
255             q = r;
256             if ((r = strchr(r, '\t')) == NULL) {
257                 warn("sorted overview file %s has a bad line at %d",
258                      SortedTmpPath, count);
259                 continue;
260             }
261             *r++ = '\0';
262         } else {
263             *p++ = '\0';
264             *q++ = '\0';
265             *r++ = '\0';
266             arrived = (time_t)atol(line);
267             expires = (time_t)atol(p);
268         }
269         token = TextToToken(q);
270         if (OVadd(token, r, strlen(r), arrived, expires) == OVADDFAILED) {
271             if (OVctl(OVSPACE, (void *)&i) && i == OV_NOSPACE) {
272                 warn("no space left for overview");
273                 OVclose();
274                 Fork ? _exit(1) : exit(1);
275             }
276             warn("cannot write overview data \"%.40s\"", q);
277         }
278     }
279     /* Check for errors and close. */
280     if (QIOerror(qp)) {
281         syswarn("cannot read sorted overview file %s", SortedTmpPath);
282         OVclose();
283         Fork ? _exit(1) : exit(1);
284     }
285     QIOclose(qp);
286     /* unlink sorted tmp file */
287     unlink(SortedTmpPath);
288     free(SortedTmpPath);
289     if(Fork) {
290         OVclose();
291         _exit(0);
292     }
293 }
294             
295
296 /*
297  * Write a line to the overview temp file. 
298  */
299 static void
300 WriteOverLine(TOKEN *token, const char *xrefs, int xrefslen, 
301               char *overdata, int overlen, time_t arrived, time_t expires)
302 {
303     char temp[SMBUF];
304     const char *p, *q, *r;
305     int i, fd;
306
307     if (sorttype == OVNOSORT) {
308         if (Fork) {
309             fprintf(Overchan, "%s %ld %ld ", TokenToText(*token), (long)arrived, (long)expires);
310             if (fwrite(overdata, 1, overlen, Overchan) != (size_t) overlen)
311                 sysdie("writing overview failed");
312             fputc('\n', Overchan);
313         } else if (OVadd(*token, overdata, overlen, arrived, expires) == OVADDFAILED) {
314             if (OVctl(OVSPACE, (void *)&i) && i == OV_NOSPACE) {
315                 warn("no space left for overview");
316                 OVclose();
317                 exit(1);
318             }
319             warn("cannot write overview data for article %s",
320                  TokenToText(*token));
321         }
322         return;
323     }
324     if (OverTmpPath == NULL) {
325         /* need new temp file, so create it. */
326         OverTmpPath = concatpath(TmpDir, "histXXXXXX");
327         fd = mkstemp(OverTmpPath);
328         if (fd < 0)
329             sysdie("cannot create temporary file");
330         OverTmpFile = fdopen(fd, "w");
331         if (OverTmpFile == NULL)
332             sysdie("cannot open %s", OverTmpPath);
333         OverTmpSegCount = 0;
334     }
335     if (sorttype == OVNEWSGROUP) {
336         /* find first ng name in xref. */
337         for (p = xrefs, q=NULL ; p < xrefs+xrefslen ; ++p) {
338             if (*p == ' ') {
339                 q = p+1; /* found space */
340                 break;
341             }
342         }
343         if (!q) {
344             warn("bogus Xref data for %s", TokenToText(*token));
345             /* XXX do nuke here? */
346             return;
347         }
348
349         for (p = q, r=NULL ; p < xrefs+xrefslen ; ++p) {
350             if (*p == ':') {
351                 r=p;
352                 break;
353             }
354         }
355         if (!r) {
356             warn("bogus Xref data for %s", TokenToText(*token));
357             /* XXX do nuke here? */
358             return;
359         }
360         /* q points to start of ng name, r points to its end. */
361         assert(sizeof(temp) > r - q + 1);
362         memcpy(temp, q, r - q + 1);
363         temp[r - q + 1] = '\0';
364         fprintf(OverTmpFile, "%s\t%10lu\t%lu\t%s\t", temp,
365                 (unsigned long) arrived, (unsigned long) expires,
366                 TokenToText(*token));
367     } else
368         fprintf(OverTmpFile, "%10lu\t%lu\t%s\t", (unsigned long) arrived,
369                 (unsigned long) expires,
370                 TokenToText(*token));
371
372     fwrite(overdata, overlen, 1, OverTmpFile);
373     fprintf(OverTmpFile, "\n");
374     OverTmpSegCount++;
375
376     if (OverTmpSegSize != 0 && OverTmpSegCount >= OverTmpSegSize) {
377         FlushOverTmpFile();
378     }
379 }
380
381
382 /*
383 **  Read the overview schema.
384 */
385 static void
386 ARTreadschema(bool Overview)
387 {
388     FILE                        *F;
389     char                        *p;
390     ARTOVERFIELD                *fp;
391     int                         i;
392     char                        buff[SMBUF];
393     bool                        foundxreffull = false;
394
395     if (Overview) {
396         /* Open file, count lines. */
397         if ((F = fopen(SchemaPath, "r")) == NULL)
398             sysdie("cannot open %s", SchemaPath);
399         for (i = 0; fgets(buff, sizeof buff, F) != NULL; i++)
400             continue;
401         fseeko(F, 0, SEEK_SET);
402         ARTfields = xmalloc((i + 1) * sizeof(ARTOVERFIELD));
403
404         /* Parse each field. */
405         for (fp = ARTfields; fgets(buff, sizeof buff, F) != NULL; ) {
406             /* Ignore blank and comment lines. */
407             if ((p = strchr(buff, '\n')) != NULL)
408                 *p = '\0';
409             if ((p = strchr(buff, '#')) != NULL)
410                 *p = '\0';
411             if (buff[0] == '\0')
412                 continue;
413             if ((p = strchr(buff, ':')) != NULL) {
414                 *p++ = '\0';
415                 fp->NeedHeadername = (strcmp(p, "full") == 0);
416             }
417             else
418                 fp->NeedHeadername = false;
419             fp->Headername = xstrdup(buff);
420             fp->HeadernameLength = strlen(buff);
421             fp->Header = (char *)NULL;
422             fp->HasHeader = false;
423             fp->HeaderLength = 0;
424             if (strncasecmp(buff, DATE, strlen(DATE)) == 0)
425                 Datep = fp;
426             if (strncasecmp(buff, MESSAGEID, strlen(MESSAGEID)) == 0)
427                 Msgidp = fp;
428             if (strncasecmp(buff, EXPIRES, strlen(EXPIRES)) == 0)
429                 Expp = fp;
430             if (strncasecmp(buff, XREF, strlen(XREF)) == 0) {
431                 Xrefp = fp;
432                 foundxreffull = fp->NeedHeadername;
433             }
434             fp++;
435         }
436         ARTfieldsize = fp - ARTfields;
437         fclose(F);
438     }
439     if (Msgidp == (ARTOVERFIELD *)NULL)
440         Missfieldsize++;
441     if (Datep == (ARTOVERFIELD *)NULL)
442         Missfieldsize++;
443     if (Expp == (ARTOVERFIELD *)NULL)
444         Missfieldsize++;
445     if (Overview && (Xrefp == (ARTOVERFIELD *)NULL || !foundxreffull))
446         die("Xref:full must be included in %s", SchemaPath);
447     if (Missfieldsize > 0) {
448         Missfields = xmalloc(Missfieldsize * sizeof(ARTOVERFIELD));
449         fp = Missfields;
450         if (Msgidp == (ARTOVERFIELD *)NULL) {
451             fp->NeedHeadername = false;
452             fp->Headername = xstrdup(MESSAGEID);
453             fp->HeadernameLength = strlen(MESSAGEID);
454             fp->Header = (char *)NULL;
455             fp->HasHeader = false;
456             fp->HeaderLength = 0;
457             Msgidp = fp++;
458         }
459         if (Datep == (ARTOVERFIELD *)NULL) {
460             fp->NeedHeadername = false;
461             fp->Headername = xstrdup(DATE);
462             fp->HeadernameLength = strlen(DATE);
463             fp->Header = (char *)NULL;
464             fp->HasHeader = false;
465             fp->HeaderLength = 0;
466             Datep = fp++;
467         }
468         if (Expp == (ARTOVERFIELD *)NULL) {
469             fp->NeedHeadername = false;
470             fp->Headername = xstrdup(EXPIRES);
471             fp->HeadernameLength = strlen(EXPIRES);
472             fp->Header = (char *)NULL;
473             fp->HasHeader = false;
474             fp->HeaderLength = 0;
475             Expp = fp++;
476         }
477         if (Overview && Xrefp == (ARTOVERFIELD *)NULL) {
478             fp->NeedHeadername = false;
479             fp->Headername = xstrdup(XREF);
480             fp->HeadernameLength = strlen(XREF);
481             fp->Header = (char *)NULL;
482             fp->HasHeader = false;
483             fp->HeaderLength = 0;
484             Xrefp = fp++;
485         }
486     }
487 }
488
489 /*
490  * Handle a single article.  This routine's fairly complicated. 
491  */
492 static void
493 DoArt(ARTHANDLE *art)
494 {
495     ARTOVERFIELD                *fp;
496     const char                  *p, *end;
497     char                        *q;
498     static struct buffer        buffer = { 0, 0, 0, NULL };
499     static char                 SEP[] = "\t";
500     static char                 NUL[] = "\0";
501     static char                 COLONSPACE[] = ": ";
502     size_t                      i, j, len;
503     const char                  *MessageID;
504     time_t                      Arrived;
505     time_t                      Expires;
506     time_t                      Posted;
507     char                        overdata[BIG_BUFFER];
508     char                        bytes[BIG_BUFFER];
509     struct artngnum             ann;
510
511     /* Set up place to store headers. */
512     for (fp = ARTfields, i = 0; i < ARTfieldsize; i++, fp++) {
513         if (fp->HeaderLength) {
514             fp->Header = 0;
515         }
516         fp->HeaderLength = 0;
517         fp->HasHeader = false;
518     }
519     if (Missfieldsize > 0) {
520         for (fp = Missfields, i = 0; i < Missfieldsize; i++, fp++) {
521             if (fp->HeaderLength) {
522                 fp->Header = 0;
523             }
524             fp->HeaderLength = 0;
525             fp->HasHeader = false;
526         }
527     }
528     for (fp = ARTfields, i = 0; i < ARTfieldsize; i++, fp++) {
529         fp->Header = wire_findheader(art->data, art->len, fp->Headername);
530
531         /* Someone managed to break their server so that they were appending
532            multiple Xref headers, and INN had a bug where it wouldn't notice
533            this and reject the article.  Just in case, see if there are
534            multiple Xref headers and use the last one. */
535         if (fp == Xrefp) {
536             const char *next = fp->Header;
537             size_t left;
538
539             while (next != NULL) {
540                 next = wire_endheader(fp->Header, art->data + art->len - 1);
541                 if (next == NULL)
542                     break;
543                 next++;
544                 left = art->len - (next - art->data);
545                 next = wire_findheader(next, left, fp->Headername);
546                 if (next != NULL)
547                     fp->Header = next;
548             }
549         }
550
551         /* Now, if we have a header, find and record its length. */
552         if (fp->Header != NULL) {
553             fp->HasHeader = true;
554             p = wire_endheader(fp->Header, art->data + art->len - 1);
555             if (p == NULL)
556                 continue;
557
558             /* The true length of the header is p - fp->Header + 1, but p
559                points to the \n at the end of the header, so subtract 2 to
560                peel off the \r\n (we're guaranteed we're dealing with
561                wire-format articles. */
562             fp->HeaderLength = p - fp->Header - 1;
563         } else if (RetrMode == RETR_ALL 
564                    && strcmp(fp->Headername, "Bytes") == 0) {
565             snprintf(bytes, sizeof(bytes), "%lu", (unsigned long) art->len);
566             fp->HasHeader = true;
567             fp->Header = bytes;
568             fp->HeaderLength = strlen(bytes);
569         }
570     }
571     if (Missfieldsize > 0) {
572         for (fp = Missfields, i = 0; i < Missfieldsize; i++, fp++) {
573             fp->Header = wire_findheader(art->data, art->len, fp->Headername);
574             if (fp->Header != NULL) {
575                 fp->HasHeader = true;
576                 p = wire_endheader(fp->Header, art->data + art->len - 1);
577                 if (p == NULL)
578                     continue;
579                 fp->HeaderLength = p - fp->Header - 1;
580             }
581         }
582     }
583     if (DoOverview && Xrefp->HeaderLength == 0) {
584         if (!SMprobe(SMARTNGNUM, art->token, (void *)&ann)) {
585             Xrefp->Header = NULL;
586             Xrefp->HeaderLength = 0;
587         } else {
588             if (ann.artnum == 0 || ann.groupname == NULL)
589                 return;
590             len = strlen(innconf->pathhost) + 1 + strlen(ann.groupname) + 1
591                 + 16 + 1;
592             if (len > BIG_BUFFER) {
593                 Xrefp->Header = NULL;
594                 Xrefp->HeaderLength = 0;
595             } else {
596                 snprintf(overdata, sizeof(overdata), "%s %s:%lu",
597                          innconf->pathhost, ann.groupname, ann.artnum);
598                 Xrefp->Header = overdata;
599                 Xrefp->HeaderLength = strlen(overdata);
600             }
601             if (ann.groupname != NULL)
602                 free(ann.groupname);
603         }
604     }
605
606     MessageID = (char *)NULL;
607     Arrived = art->arrived;
608     Expires = 0;
609     Posted = 0;
610
611     if (!Msgidp->HasHeader) {
612         warn("no Message-ID header in %s", TokenToText(*art->token));
613         if (NukeBadArts)
614             SMcancel(*art->token);
615         return;
616     }
617
618     buffer_set(&buffer, Msgidp->Header, Msgidp->HeaderLength);
619     buffer_append(&buffer, NUL, 1);
620     for (i = 0, q = buffer.data; i < buffer.left; q++, i++)
621         if (*q == '\t' || *q == '\n' || *q == '\r')
622             *q = ' ';
623     MessageID = GetMessageID(buffer.data);
624     if (*MessageID == '\0') {
625         warn("no Message-ID header in %s", TokenToText(*art->token));
626         if (NukeBadArts)
627             SMcancel(*art->token);
628         return;
629     }
630
631     /*
632      * check if msgid is in history if in update mode, or if article is 
633      * newer than start time of makehistory. 
634      */
635
636     if (!Datep->HasHeader) {
637         Posted = Arrived;
638     } else {
639         buffer_set(&buffer, Datep->Header, Datep->HeaderLength);
640         buffer_append(&buffer, NUL, 1);
641         for (i = 0, q = buffer.data; i < buffer.left; q++, i++)
642             if (*q == '\t' || *q == '\n' || *q == '\r')
643                 *q = ' ';
644         if ((Posted = GetaDate(buffer.data)) == 0)
645             Posted = Arrived;
646     }
647
648     if (Expp->HasHeader) {
649         buffer_set(&buffer, Expp->Header, Expp->HeaderLength);
650         buffer_append(&buffer, NUL, 1);
651         for (i = 0, q = buffer.data; i < buffer.left; q++, i++)
652             if (*q == '\t' || *q == '\n' || *q == '\r')
653                 *q = ' ';
654         Expires = GetaDate(buffer.data);
655     }
656
657     if (DoOverview && Xrefp->HeaderLength > 0) {
658         for (fp = ARTfields, j = 0; j < ARTfieldsize; j++, fp++) {
659             if (fp == ARTfields)
660                 buffer_set(&buffer, "", 0);
661             else
662                 buffer_append(&buffer, SEP, strlen(SEP));
663             if (fp->HeaderLength == 0)
664                 continue;
665             if (fp->NeedHeadername) {
666                 buffer_append(&buffer, fp->Headername, fp->HeadernameLength);
667                 buffer_append(&buffer, COLONSPACE, strlen(COLONSPACE));
668             }
669             i = buffer.left;
670             buffer_resize(&buffer, buffer.left + fp->HeaderLength);
671             end = fp->Header + fp->HeaderLength - 1;
672             for (p = fp->Header, q = &buffer.data[i]; p <= end; p++) {
673                 if (*p == '\r' && p < end && p[1] == '\n') {
674                     p++;
675                     continue;
676                 }
677                 if (*p == '\0' || *p == '\t' || *p == '\n' || *p == '\r')
678                     *q++ = ' ';
679                 else
680                     *q++ = *p;
681                 buffer.left++;
682             }
683         }
684         WriteOverLine(art->token, Xrefp->Header, Xrefp->HeaderLength,
685                       buffer.data, buffer.left, Arrived, Expires);
686     }
687
688     if (!NoHistory) {
689         bool r;
690
691         r = HISwrite(History, MessageID,
692                      Arrived, Posted, Expires, art->token);
693         if (r == false)
694             sysdie("cannot write history line");
695     }
696 }
697
698
699 /*
700 ** Add all groups to overview group.index. --rmt
701 */
702 static void
703 OverAddAllNewsgroups(void)
704 {
705     QIOSTATE *qp;
706     int count;
707     char *q,*p;
708     char *line;
709     ARTNUM hi, lo;
710
711     if ((qp = QIOopen(ActivePath)) == NULL)
712         sysdie("cannot open %s", ActivePath);
713     for (count = 1; (line = QIOread(qp)) != NULL; count++) {
714         if ((p = strchr(line, ' ')) == NULL) {
715             warn("bad active line %d: %.40s", count, line);
716             continue;
717         }
718         *p++ = '\0';
719         hi = (ARTNUM)atol(p);
720         if ((p = strchr(p, ' ')) == NULL) {
721             warn("bad active line %d: %.40s", count, line);
722             continue;
723         }
724         *p++ = '\0';
725         lo = (ARTNUM)atol(p);
726         if ((q = strrchr(p, ' ')) == NULL) {
727             warn("bad active line %d: %.40s", count, line);
728             continue;
729         }
730         /* q+1 points to NG flag */
731         if (!OVgroupadd(line, lo, hi, q+1))
732             die("cannot add %s to overview group index", line);
733     }
734     /* Test error conditions; QIOtoolong shouldn't happen. */
735     if (QIOtoolong(qp))
736         die("active file line %d is too long", count);
737     if (QIOerror(qp))
738         sysdie("cannot read %s around line %d", ActivePath, count);
739     QIOclose(qp);
740 }
741
742
743 /*
744 **  Change to the news user if possible, and if not, die.  Used for operations
745 **  that may create new database files, so as not to mess up the ownership.
746 */
747 static void
748 setuid_news(void)
749 {
750     struct passwd *pwd;
751
752     pwd = getpwnam(NEWSUSER);
753     if (pwd == NULL)
754         die("can't resolve %s to a UID (account doesn't exist?)", NEWSUSER);
755     if (getuid() == 0)
756         setuid(pwd->pw_uid);
757     if (getuid() != pwd->pw_uid)
758         die("must be run as %s", NEWSUSER);
759 }
760
761
762 int
763 main(int argc, char **argv)
764 {
765     ARTHANDLE *art = NULL;
766     bool AppendMode;
767     int i;
768     bool val;
769     char *HistoryDir;
770     char *p;
771     char *buff;
772     size_t npairs = 0;
773
774     /* First thing, set up logging and our identity. */
775     openlog("makehistory", L_OPENLOG_FLAGS | LOG_PID, LOG_INN_PROG);
776     message_program_name = "makehistory";
777         
778     /* Set defaults. */
779     if (!innconf_read(NULL))
780         exit(1);
781     HistoryPath = concatpath(innconf->pathdb, _PATH_HISTORY);
782     ActivePath = concatpath(innconf->pathdb, _PATH_ACTIVE);
783     TmpDir = innconf->pathtmp;
784     SchemaPath = concatpath(innconf->pathetc, _PATH_SCHEMA);
785
786     OverTmpSegSize = DEFAULT_SEGSIZE;
787     OverTmpSegCount = 0;
788     NukeBadArts = false;
789     DoOverview = false;
790     Fork = false;
791     AppendMode = false;
792     NoHistory = false;
793     RetrMode = RETR_HEAD;
794
795     while ((i = getopt(argc, argv, "aebf:Il:OT:xFs:")) != EOF) {
796         switch(i) {
797         case 'T':
798             TmpDir = optarg;
799             break;
800         case 'x':
801             NoHistory = true;
802             break;
803         case 'a':
804             AppendMode = true;
805             break;
806         case 'b':
807             NukeBadArts = true;
808             break;
809         case 'f':
810             HistoryPath = optarg;
811             break;
812         case 'I':
813             Cutofflow = true;
814             break;
815         case 'l':
816             OverTmpSegSize = atoi(optarg);
817             break;
818         case 'O':
819             DoOverview = true;
820             break;
821         case 'F':
822             Fork = true;
823             break;
824         case 'e':
825             RetrMode = RETR_ALL;
826             break;
827         case 's':
828             npairs = atoi(optarg);
829             break;
830             
831         default:
832             fprintf(stderr, "%s", usage);
833             exit(1);
834             break;
835         }
836     }
837     argc -= optind;
838     argv += optind;
839     if (argc) {
840         fprintf(stderr, "%s", usage);
841         exit(1);
842     }
843
844     if ((p = strrchr(HistoryPath, '/')) == NULL) {
845         /* find the default history file directory */
846         HistoryDir = innconf->pathdb;
847     } else {
848         *p = '\0';
849         HistoryDir = xstrdup(HistoryPath);
850         *p = '/';
851     }
852
853     if (chdir(HistoryDir) < 0)
854         sysdie("cannot chdir to %s", HistoryDir);
855
856     /* Change users if necessary. */
857     setuid_news();
858
859     /* Read in the overview schema */
860     ARTreadschema(DoOverview);
861     
862     if (DoOverview) {
863         /* init the overview setup. */
864         if (!OVopen(OV_WRITE))
865             sysdie("cannot open overview");
866         if (!OVctl(OVSORT, (void *)&sorttype))
867             die("cannot obtain overview sort information");
868         if (!Fork) {
869             if (!OVctl(OVCUTOFFLOW, (void *)&Cutofflow))
870                 die("cannot obtain overview cutoff information");
871             OverAddAllNewsgroups();
872         } else {
873             OverAddAllNewsgroups();
874             if (sorttype == OVNOSORT) {
875                 buff = concat(innconf->pathbin, "/", "overchan", NULL);
876                 if ((Overchan = popen(buff, "w")) == NULL)
877                     sysdie("cannot fork overchan process");
878                 free(buff);
879             }
880             OVclose();
881         }
882     }
883
884     /* Init the Storage Manager */
885     val = true;
886     if (!SMsetup(SM_RDWR, (void *)&val) || !SMsetup(SM_PREOPEN, (void *)&val))
887         sysdie("cannot set up storage manager");
888     if (!SMinit())
889         sysdie("cannot initialize storage manager: %s", SMerrorstr);
890
891     /* Initialise the history manager */
892     if (!NoHistory) {
893         int flags = HIS_RDWR | HIS_INCORE;
894
895         if (!AppendMode)
896             flags |= HIS_CREAT;
897         History = HISopen(NULL, innconf->hismethod, flags);
898         if (History == NULL)
899             sysdie("cannot create history handle");
900         HISctl(History, HISCTLS_NPAIRS, &npairs);
901         if (!HISctl(History, HISCTLS_PATH, HistoryPath))
902             sysdie("cannot open %s", HistoryPath);
903     }
904
905     /* Get the time.  Only get it once, which is good enough. */
906     if (GetTimeInfo(&Now) < 0)
907         sysdie("cannot get the time");
908
909     /*
910      * Scan the entire spool, nuke any bad arts if needed, and process each
911      * article.
912      */
913         
914     while ((art = SMnext(art, RetrMode)) != NULL) {
915         if (art->len == 0) {
916             if (NukeBadArts && art->data == NULL && art->token != NULL)
917                 SMcancel(*art->token);
918             continue;
919         }
920         DoArt(art);
921     }
922
923     if (!NoHistory) {
924         /* close history file. */
925         if (!HISclose(History))
926             sysdie("cannot close history file");
927     }
928
929     if (DoOverview) {
930         if (sorttype == OVNOSORT && Fork)
931             if (fflush(Overchan) == EOF || ferror(Overchan) || pclose(Overchan) == EOF)
932                 sysdie("cannot flush overview data");
933         if (sorttype != OVNOSORT) {
934             int status;
935             FlushOverTmpFile();
936             if(Fork)
937                 wait(&status);
938         }
939     }
940     if(!Fork)
941         OVclose();
942     exit(0);
943 }
944