chiark / gitweb /
Debianization and various other fixes.
[ezmlm] / ezmlm-archive.c
1 /*$Id: ezmlm-archive.c,v 1.13 1999/11/28 20:13:32 lindberg Exp $*/
2 /*$Name: ezmlm-idx-040 $*/
3
4 #include "alloc.h"
5 #include "error.h"
6 #include "stralloc.h"
7 #include "str.h"
8 #include "sig.h"
9 #include "getconf.h"
10 #include "strerr.h"
11 #include "getln.h"
12 #include "substdio.h"
13 #include "readwrite.h"
14 #include "fmt.h"
15 #include "sgetopt.h"
16 #include "idxthread.h"
17 #include "makehash.h"
18 #include "idx.h"
19 #include "errtxt.h"
20
21 #define FATAL "ezmlm-archive: fatal: "
22 #define WARNING "ezmlm-archive: warning: inconsistent index: "
23
24 substdio ssin;
25 char inbuf[1024];
26 substdio ssout;
27 char outbuf[1024];
28 substdio ssnum;
29 char numbuf[16];
30
31 stralloc line = {0};
32 stralloc num = {0};
33 stralloc fn = {0};
34 stralloc fnn = {0};
35
36 char strnum[FMT_ULONG];
37 int flagerror = 0;
38 int flagsync = 1;       /* sync() by default, not for -c or -f or -t */
39 char *dir;
40
41 struct ca {
42   char *s;              /* start */
43   unsigned int l;       /* length */
44 } ca;
45
46 void die_usage() {
47   strerr_die1x(100,
48     "ezmlm-archive: usage: "
49         "ezmlm-archive [-cCFsSTvV] [-f min_msg] [-t max_msg] dir");
50 }
51
52 void die_nomem() { strerr_die2x(111,FATAL,ERR_NOMEM); }
53
54 void close_proper(ss,s,sn)
55 /* flush,sync,close,move sn->s) */
56 substdio *ss;
57 char *s, *sn;
58 {
59    if (substdio_flush(ss) == -1)
60      strerr_die6sys(111,FATAL,ERR_FLUSH,dir,"/",s,": ");
61   if (flagsync)
62     if (fsync(ss->fd) == -1)
63        strerr_die6sys(111,FATAL,ERR_SYNC,dir,"/",s,": ");
64   if (close(ss->fd) == -1)
65      strerr_die6sys(111,FATAL,ERR_CLOSE,dir,"/",s,": ");
66   if (rename(sn,s) == -1)
67      strerr_die6sys(111,FATAL,ERR_MOVE,dir,"/",sn,": ");
68 }
69
70 void write_threads(msgtable,subtable,authtable,datetable,from,to)
71 /* Add the current threading data to the thread database without dups */
72 /* Writes the subject index first, then processes the individual files */
73 msgentry *msgtable; subentry *subtable; authentry *authtable;
74 dateentry *datetable;
75 unsigned long from,to;
76 {
77   msgentry *pmsgt;
78   subentry *psubt,*psubtm, *psubtlast;
79   subentry *presubt = (subentry *)0;
80   authentry *pautht;
81   dateentry *pdatet;
82   char *cp,*cp1;
83   unsigned long msg;
84   unsigned long ulmsginthread;
85   unsigned long subnum;
86   unsigned long authnum;
87   unsigned long msgnum;
88   unsigned int pos,l;
89   unsigned int startdate,nextdate;
90   unsigned int startmsg,nextmsg;
91   int fd = -1;
92   int fdn = -1;
93   int match;
94   int ffound;
95   int lineno;
96   int res;
97
98   psubtm = subtable;            /* now for new threads */
99   pdatet = datetable;
100   nextmsg = 0L;
101   nextdate = pdatet->date;
102   while (psubtm->sub) {         /* these are in msgnum order */
103     if (!presubt)               /* for rewind */
104       if (psubtm->lastmsg >= nextmsg)
105         presubt = psubtm;       /* this thread extends beyond current month */
106     if (psubtm->firstmsg >= nextmsg) {  /* done with this month */
107       if (fdn != -1) close_proper(&ssout,fn.s,fnn.s);
108       psubtlast = psubtm;               /* last thread done */
109       if (presubt)                      /* need to rewind? */
110         psubtm = presubt;               /* do it */
111       psubt = psubtm;                   /* tmp pointer to reset done flag */
112       presubt = (subentry *)0;          /* reset rewind pointer */
113       pdatet++;                         /* next month */
114       startdate = nextdate;             /* startdate */
115       nextdate = pdatet->date;          /* end date */
116       startmsg = nextmsg;               /* first message in month */
117       nextmsg = pdatet->msg;            /* first message in next month */
118       if (!stralloc_copys(&fn,"archive/threads/")) die_nomem();
119       if (!stralloc_catb(&fn,strnum,fmt_uint(strnum,startdate))) die_nomem();
120       if (!stralloc_copy(&fnn,&fn)) die_nomem();
121       if (!stralloc_0(&fn)) die_nomem();
122       if (!stralloc_cats(&fnn,"n")) die_nomem();
123       if (!stralloc_0(&fnn)) die_nomem();
124       if ((fdn = open_trunc(fnn.s)) == -1)
125         strerr_die6sys(111,FATAL,ERR_CREATE,dir,"/",fnn.s,": ");
126       substdio_fdbuf(&ssout,write,fdn,outbuf,sizeof(outbuf));
127       if ((fd = open_read(fn.s)) == -1) {
128       if (errno != error_noent)
129              strerr_die6sys(111,FATAL,ERR_OPEN,dir,"/",fn.s,": ");
130       } else {
131         substdio_fdbuf(&ssin,read,fd,inbuf,sizeof(inbuf));
132       for (;;) {
133       if (getln(&ssin,&line,&match,'\n') == -1)
134              strerr_die6sys(111,FATAL,ERR_READ,dir,"/",fn.s,": ");
135       if (!match) break;
136       pos = scan_ulong(line.s,&msgnum);
137       pos++;                    /* skip ':' */
138       if (msgnum >= from)
139         continue;               /* ignore entries from threading range */
140       if (line.len < pos + HASHLEN) {
141         flagerror = -1;         /* and bad ones */
142         continue;
143       }
144       psubt = subtable;
145       cp = line.s + pos;
146       ffound = 0;               /* search among already known subjects */
147       for (;;) {
148         res = str_diffn(psubt->sub,cp,HASHLEN);
149         if (res < 0) {
150           if (psubt->higher)
151             psubt = psubt->higher;
152          else
153            break;
154         } else if (res > 0) {
155           if (psubt->lower)
156             psubt = psubt->lower;
157           else
158             break;
159         } else {
160           ffound = 1;
161           break;
162         }
163       }
164       if (!ffound) {
165         if (substdio_put(&ssout,line.s,line.len) == -1)
166           strerr_die6sys(111,FATAL,ERR_WRITE,dir,"/",fnn.s,": ");
167       } else {                  /* new # of msg in thread */
168         cp += HASHLEN;          /* HASHLEN [#] Subject always \n at end */
169         if (*(cp++) == ' ' && *(cp++) == '[') {
170           cp += scan_ulong(cp,&ulmsginthread);
171           if (*cp == ']') {
172             psubt->msginthread += (unsigned char) (ulmsginthread & 0xff);
173           }
174         } else
175           flagerror = -5;
176       }
177     }
178     close(fd);
179   }
180   continue;
181   }
182
183     if (psubtm->firstmsg < nextmsg && psubtm->lastmsg >= startmsg) {
184     if (!stralloc_copyb(&line,strnum,fmt_ulong(strnum,psubtm->lastmsg)))
185                 die_nomem();
186     if (!stralloc_cats(&line,":")) die_nomem();
187     if (!stralloc_catb(&line,psubtm->sub,HASHLEN)) die_nomem();
188     if (!stralloc_cats(&line," [")) die_nomem();
189     if (!stralloc_catb(&line,strnum,
190         fmt_ulong(strnum,(unsigned long) psubtm->msginthread)))
191                 die_nomem();
192     if (!stralloc_cats(&line,"]")) die_nomem();
193     if (!stralloc_catb(&line,psubtm->sub + HASHLEN,psubtm->sublen - HASHLEN))
194                          die_nomem();   /* has \n */
195     if (substdio_put(&ssout,line.s,line.len) == -1)
196         strerr_die6sys(111,FATAL,ERR_WRITE,dir,"/",fnn.s,": ");
197     }
198   psubtm++;
199   }
200   if (fdn != -1)
201     close_proper(&ssout,fn.s,fnn.s);
202
203   psubt = subtable;
204   while (psubt->sub) {          /* now the threads */
205     if (!stralloc_copys(&fn,"archive/subjects/")) die_nomem();
206     if (!stralloc_catb(&fn,psubt->sub,2)) die_nomem();
207     if (!stralloc_0(&fn)) die_nomem();
208     if (mkdir(fn.s,0755) == -1)
209     if (errno != error_exist)
210       strerr_die6sys(111,FATAL,ERR_CREATE,dir,"/",fn.s,": ");
211     fn.s[fn.len - 1] = '/';
212     if (!stralloc_catb(&fn,psubt->sub+2,HASHLEN-2)) die_nomem();
213     if (!stralloc_copy(&fnn,&fn)) die_nomem();
214     if (!stralloc_cats(&fnn,"n")) die_nomem();
215     if (!stralloc_0(&fn)) die_nomem();
216     if (!stralloc_0(&fnn)) die_nomem();
217     if ((fdn = open_trunc(fnn.s)) == -1)
218       strerr_die4sys(111,FATAL,ERR_CREATE,fnn.s,": ");
219     substdio_fdbuf(&ssout,write,fdn,outbuf,sizeof(outbuf));
220     if ((fd = open_read(fn.s)) == -1) {
221       if (errno != error_noent)
222           strerr_die4sys(111,FATAL,ERR_OPEN,fn.s,": ");
223       if (substdio_puts(&ssout,psubt->sub) == -1)       /* write subject */
224              strerr_die6sys(111,FATAL,ERR_WRITE,dir,"/",fnn.s,": ");
225     } else {                                    /* copy data */
226         substdio_fdbuf(&ssin,read,fd,inbuf,sizeof(inbuf));
227         lineno = 0;
228         for (;;) {
229           if (getln(&ssin,&line,&match,'\n') == -1)
230              strerr_die6sys(111,FATAL,ERR_READ,dir,"/",fn.s,": ");
231           if (!match) break;
232           if (!lineno) {                        /* write subject */
233             if (line.len < HASHLEN + 1 || line.s[HASHLEN] != ' ')
234                 flagerror = -3;
235             if (substdio_put(&ssout,line.s,line.len) == -1)
236                strerr_die6sys(111,FATAL,ERR_WRITE,dir,"/",fnn.s,": ");
237             lineno = 1;
238             continue;
239           }
240           (void) scan_ulong(line.s,&msgnum);
241           if (msgnum >= from) break;
242           if (substdio_put(&ssout,line.s,line.len) == -1)
243              strerr_die6sys(111,FATAL,ERR_WRITE,dir,"/",fnn.s,": ");
244         }
245         (void) close(fd);       /* close old index */
246       }
247
248     subnum = (unsigned long) (psubt - subtable + 1);    /* idx of this subj */
249     pmsgt = msgtable + psubt->firstmsg - from;  /* first message entry */
250     for (msg = psubt->firstmsg; msg <= psubt->lastmsg; msg++) {
251       if (pmsgt->subnum == subnum) {
252         if (!stralloc_copyb(&line,strnum,fmt_ulong(strnum,msg))) die_nomem();
253         if (!stralloc_cats(&line,":")) die_nomem();
254         if (!stralloc_catb(&line,strnum,fmt_uint(strnum,pmsgt->date)))
255                 die_nomem();
256         if (!stralloc_cats(&line,":")) die_nomem();
257         if (pmsgt->authnum) {
258           pautht = authtable + pmsgt->authnum - 1;
259           cp = pautht->auth;
260           cp1 = cp + str_chr(cp,' ');
261           if (cp + HASHLEN != cp1)
262             strerr_die1x(100,ERR_BAD_INDEX);
263           if (!stralloc_cats(&line,cp))
264                 die_nomem();                            /* hash */
265         } else
266           if (!stralloc_cats(&line,"\n")) die_nomem();
267         if (substdio_put(&ssout,line.s,line.len) == -1)
268           strerr_die6sys(111,FATAL,ERR_WRITE,dir,"/",fnn.s,": ");
269       }
270       pmsgt++;
271     }
272     close_proper(&ssout,fn.s,fnn.s);
273     psubt++;
274   }
275
276                                         /* (no master author index) */
277   pautht = authtable;
278   while (pautht->auth) {                /* now the authors */
279     if (!stralloc_copys(&fn,"archive/authors/")) die_nomem();
280     if (!stralloc_catb(&fn,pautht->auth,2)) die_nomem();
281     if (!stralloc_0(&fn)) die_nomem();
282     if (mkdir(fn.s,0755) == -1)
283     if (errno != error_exist)
284       strerr_die6sys(111,FATAL,ERR_CREATE,dir,"/",fn.s,": ");
285     fn.s[fn.len - 1] = '/';
286     if (!stralloc_catb(&fn,pautht->auth+2,HASHLEN-2)) die_nomem();
287     if (!stralloc_copy(&fnn,&fn)) die_nomem();
288     if (!stralloc_cats(&fnn,"n")) die_nomem();
289     if (!stralloc_0(&fn)) die_nomem();
290     if (!stralloc_0(&fnn)) die_nomem();
291     if ((fdn = open_trunc(fnn.s)) == -1)
292       strerr_die4sys(111,FATAL,ERR_CREATE,fnn.s,": ");
293     substdio_fdbuf(&ssout,write,fdn,outbuf,sizeof(outbuf));
294       if ((fd = open_read(fn.s)) == -1) {
295         if (errno != error_noent)
296           strerr_die4sys(111,FATAL,ERR_OPEN,fn.s,": ");
297         else {                  /* didn't exist before: write author */
298           if (substdio_put(&ssout,pautht->auth,pautht->authlen) == -1)
299              strerr_die6sys(111,FATAL,ERR_WRITE,dir,"/",fnn.s,": ");
300         }
301       } else {                                  /* copy data */
302         substdio_fdbuf(&ssin,read,fd,inbuf,sizeof(inbuf));
303         lineno = 0;
304         for (;;) {
305           if (getln(&ssin,&line,&match,'\n') == -1)
306              strerr_die6sys(111,FATAL,ERR_READ,dir,"/",fn.s,": ");
307           if (!match) break;
308           if (!lineno) {                        /* write author */
309             if (line.len < HASHLEN + 1 || line.s[HASHLEN] != ' ')
310                 flagerror = - 4;
311             if (substdio_put(&ssout,line.s,line.len) == -1)
312                strerr_die6sys(111,FATAL,ERR_WRITE,dir,"/",fnn.s,": ");
313             lineno = 1;
314             continue;
315           }
316           (void) scan_ulong(line.s,&msgnum);
317           if (msgnum >= from) break;
318           if (substdio_put(&ssout,line.s,line.len) == -1)
319              strerr_die6sys(111,FATAL,ERR_WRITE,dir,"/",fnn.s,": ");
320         }
321         (void) close(fd);                       /* close old index */
322       }
323
324     authnum = (unsigned long) (pautht - authtable + 1); /* idx of this auth */
325     pmsgt = msgtable + pautht->firstmsg - from; /* first message entry */
326     for (msg = pautht->firstmsg; msg <= to; msg++) {
327       if (pmsgt->authnum == authnum) {
328         if (!stralloc_copyb(&line,strnum,fmt_ulong(strnum,msg))) die_nomem();
329         if (!stralloc_cats(&line,":")) die_nomem();
330         if (!stralloc_catb(&line,strnum,fmt_uint(strnum,pmsgt->date)))
331                 die_nomem();
332         if (!stralloc_cats(&line,":")) die_nomem();
333         if (pmsgt->subnum) {
334           psubt = subtable + pmsgt->subnum - 1;
335           if (!stralloc_catb(&line,psubt->sub,psubt->sublen))
336                 die_nomem();
337         }
338         if (substdio_put(&ssout,line.s,line.len) == -1)
339           strerr_die6sys(111,FATAL,ERR_WRITE,dir,"/",fnn.s,": ");
340       }
341       pmsgt++;
342     }
343     close_proper(&ssout,fn.s,fnn.s);
344     pautht++;
345   }
346 }
347
348 int main(argc,argv)
349 int argc;
350 char **argv;
351 {
352   unsigned long archnum = 0L;
353   unsigned long to = 0L;
354   unsigned long max;
355   int fd;
356   int fdlock;
357   int flagcreate = 0;
358   int flagsyncall = 0;
359   int opt;
360   msgentry *msgtable;
361   subentry *subtable;
362   authentry *authtable;
363   dateentry *datetable;
364
365   (void) umask(022);
366   sig_pipeignore();
367
368   while ((opt = getopt(argc,argv,"cCf:FsSt:TvV")) != opteof)
369     switch (opt) {
370       case 'c': flagcreate = 1;
371                 flagsync = 0;
372                 break;                  /* start at beginning of archive */
373       case 'C': flagcreate = 0;
374                 break;  /* Do only archnum+1 => num */
375       case 'f': if (optarg) {
376                   (void) scan_ulong(optarg,&archnum);
377                   archnum = (archnum / 100) * 100;
378                 }
379                 flagsync = 0;
380                 break;
381       case 'F': archnum = 0; break;
382       case 's': flagsyncall = 1; break;
383       case 'S': flagsyncall = 0; break;
384       case 't': if (optarg) {
385                   (void) scan_ulong(optarg,&to);
386                 }
387                 flagsync = 0;
388                 break;
389       case 'T': to = 0; break;
390       case 'v':
391       case 'V': strerr_die2x(0,"ezmlm-archive version: ",EZIDX_VERSION);
392       default:
393         die_usage();
394     }
395
396   if (flagsyncall) flagsync = 1;        /* overrides */
397   dir = argv[optind++];
398   if (!dir) die_usage();
399   if (chdir(dir) == -1)
400     strerr_die4sys(111,FATAL,ERR_SWITCH,dir,": ");
401
402   if (mkdir("archive/threads",0755) == -1)
403     if (errno != error_exist)
404       strerr_die4sys(111,FATAL,ERR_CREATE,dir,"/archive/threads: ");
405   if (mkdir("archive/subjects",0755) == -1)
406     if (errno != error_exist)
407       strerr_die4sys(111,FATAL,ERR_CREATE,dir,"/archive/subjects: ");
408   if (mkdir("archive/authors",0755) == -1)
409     if (errno != error_exist)
410       strerr_die4sys(111,FATAL,ERR_CREATE,dir,"/archive/authors: ");
411
412         /* Lock list to assure that no ezmlm-send is working on it */
413         /* and that the "num" message is final */
414   fdlock = open_append("lock");
415   if (fdlock == -1)
416     strerr_die2sys(111,FATAL,ERR_OPEN_LOCK);
417   if (lock_ex(fdlock) == -1) {
418     (void) close(fdlock);
419     strerr_die2sys(111,FATAL,ERR_OBTAIN_LOCK);
420   }
421                                         /* get num */
422   if (!getconf_line(&num,"num",0,FATAL,dir))
423     strerr_die1x(100,ERR_EMPTY_LIST);
424   (void) close(fdlock);
425
426   if (!stralloc_0(&num)) die_nomem();   /* parse num */
427   (void) scan_ulong(num.s,&max);
428   if (!to || to > max) to = max;
429
430   fdlock = open_append("archive/lock"); /* lock index */
431   if (fdlock == -1)
432     strerr_die4sys(111,FATAL,ERR_OPEN,dir,"/archive/lock: ");
433   if (lock_ex(fdlock) == -1) {
434     (void) close(fdlock);
435     strerr_die4sys(111,FATAL,ERR_OBTAIN,dir,"/archive/lock: ");
436   }
437   if (!flagcreate && !archnum) {        /* adjust archnum (from) / to */
438     if (getconf_line(&num,"archnum",0,FATAL,dir)) {
439       if (!stralloc_0(&num)) die_nomem();
440       (void) scan_ulong(num.s,&archnum);
441       archnum++;
442     }
443   }
444
445   if (archnum > to)
446     _exit(0);                           /* nothing to do */
447
448                                         /* do the subject threading */
449   idx_mkthreads(&msgtable,&subtable,&authtable,&datetable,
450         archnum,to,max,0,FATAL);
451                                         /* update the index */
452   write_threads(msgtable,subtable,authtable,datetable,archnum,to);
453                                         /* update archnum */
454   if ((fd = open_trunc("archnumn")) == -1)
455     strerr_die4sys(111,FATAL,ERR_CREATE,dir,"/archnumn: ");
456   substdio_fdbuf(&ssnum,write,fd,numbuf,sizeof(numbuf));
457   if (substdio_put(&ssnum,strnum,fmt_ulong(strnum,to)) == -1)
458      strerr_die6sys(111,FATAL,ERR_WRITE,dir,"/",fnn.s,": ");
459   if (substdio_puts(&ssnum,"\n") == -1)
460      strerr_die6sys(111,FATAL,ERR_WRITE,dir,"/",fnn.s,": ");
461   close_proper(&ssnum,"archnum","archnumn");
462   switch (flagerror) {
463     case 0:
464        _exit(0);                                /* go bye-bye */
465     case -1:
466        strerr_die2x(99,WARNING,"threads entry with illegal format");
467     case -2:
468        strerr_die2x(99,WARNING,"thread in index, but threadfile missing");
469     case -3:
470        strerr_die2x(99,WARNING,"a subject file lacks subject");
471     case -4:
472        strerr_die2x(99,WARNING,"an author file lacks author/hash");
473     case -5:
474        strerr_die2x(99,WARNING,"threads entry lacks message count");
475     default:
476        strerr_die2x(99,WARNING,"something happened that isn't quite right");
477   }
478 }
479