chiark / gitweb /
Commit 2.4.5-5 as unpacked
[inn-innduct.git] / backends / archive.c
1 /*  $Id: archive.c 6138 2003-01-19 04:13:51Z rra $
2 **
3 **  Read batchfiles on standard input and archive them.
4 */
5
6 #include "config.h"
7 #include "clibrary.h"
8 #include <ctype.h>
9 #include <errno.h>
10 #include <sys/stat.h>
11 #include <time.h>
12
13 #ifdef TM_IN_SYS_TIME
14 # include <sys/time.h>
15 #endif
16
17 #include "inn/innconf.h"
18 #include "inn/messages.h"
19 #include "inn/wire.h"
20 #include "libinn.h"
21 #include "paths.h"
22 #include "storage.h"
23
24
25 static char     *Archive = NULL;
26 static char     *ERRLOG = NULL;
27
28 /*
29 **  Return a YYYYMM string that represents the current year/month
30 */
31 static char *
32 DateString(void)
33 {
34     static char         ds[10];
35     time_t              now;
36     struct tm           *x;
37
38     time(&now);
39     x = localtime(&now);
40     snprintf(ds, sizeof(ds), "%d%d", x->tm_year + 1900, x->tm_mon + 1);
41
42     return ds;
43 }
44
45
46 /*
47 **  Try to make one directory.  Return false on error.
48 */
49 static bool
50 MakeDir(char *Name)
51 {
52     struct stat         Sb;
53
54     if (mkdir(Name, GROUPDIR_MODE) >= 0)
55         return true;
56
57     /* See if it failed because it already exists. */
58     return stat(Name, &Sb) >= 0 && S_ISDIR(Sb.st_mode);
59 }
60
61
62 /*
63 **  Given an entry, comp/foo/bar/1123, create the directory and all
64 **  parent directories needed.  Return false on error.
65 */
66 static bool
67 MakeArchiveDirectory(char *Name)
68 {
69     char        *p;
70     char        *save;
71     bool                made;
72
73     if ((save = strrchr(Name, '/')) != NULL)
74         *save = '\0';
75
76     /* Optimize common case -- parent almost always exists. */
77     if (MakeDir(Name)) {
78         if (save)
79             *save = '/';
80         return true;
81     }
82
83     /* Try to make each of comp and comp/foo in turn. */
84     for (p = Name; *p; p++)
85         if (*p == '/' && p != Name) {
86             *p = '\0';
87             made = MakeDir(Name);
88             *p = '/';
89             if (!made) {
90                 if (save)
91                     *save = '/';
92                 return false;
93             }
94         }
95
96     made = MakeDir(Name);
97     if (save)
98         *save = '/';
99     return made;
100 }
101
102
103 /*
104 **  Copy a file.  Return false if error.
105 */
106 static bool
107 Copy(char *src, char *dest)
108 {
109     FILE        *in;
110     FILE        *out;
111     size_t      i;
112     char        *p;
113     char        buff[BUFSIZ];
114
115     /* Open the output file. */
116     if ((out = fopen(dest, "w")) == NULL) {
117         /* Failed; make any missing directories and try again. */
118         if ((p = strrchr(dest, '/')) != NULL) {
119             if (!MakeArchiveDirectory(dest)) {
120                 syswarn("cannot mkdir for %s", dest);
121                 return false;
122             }
123             out = fopen(dest, "w");
124         }
125         if (p == NULL || out == NULL) {
126             syswarn("cannot open %s for writing", dest);
127             return false;
128         }
129     }
130
131     /* Opening the input file is easier. */
132     if ((in = fopen(src, "r")) == NULL) {
133         syswarn("cannot open %s for reading", src);
134         fclose(out);
135         unlink(dest);
136         return false;
137     }
138
139     /* Write the data. */
140     while ((i = fread(buff, 1, sizeof buff, in)) != 0)
141         if (fwrite(buff, 1, i, out) != i) {
142             syswarn("cannot write to %s", dest);
143             fclose(in);
144             fclose(out);
145             unlink(dest);
146             return false;
147         }
148     fclose(in);
149
150     /* Flush and close the output. */
151     if (ferror(out) || fflush(out) == EOF) {
152         syswarn("cannot flush %s", dest);
153         unlink(dest);
154         fclose(out);
155         return false;
156     }
157     if (fclose(out) == EOF) {
158         syswarn("cannot close %s", dest);
159         unlink(dest);
160         return false;
161     }
162
163     return true;
164 }
165
166
167 /*
168 **  Copy an article from memory into a file.
169 */
170 static bool
171 CopyArt(ARTHANDLE *art, char *dest, bool Concat)
172 {
173     FILE        *out;
174     const char          *p;
175     char                *q, *article;
176     size_t              i;
177     const char          *mode = "w";
178
179     if (Concat) mode = "a";
180
181     /* Open the output file. */
182     if ((out = fopen(dest, mode)) == NULL) {
183         /* Failed; make any missing directories and try again. */
184         if ((p = strrchr(dest, '/')) != NULL) {
185             if (!MakeArchiveDirectory(dest)) {
186                 syswarn("cannot mkdir for %s", dest);
187                 return false;
188             }
189             out = fopen(dest, mode);
190         }
191         if (p == NULL || out == NULL) {
192             syswarn("cannot open %s for writing", dest);
193             return false;
194         }
195     }
196
197     /* Copy the data. */
198     article = xmalloc(art->len);
199     for (i=0, q=article, p=art->data; p<art->data+art->len;) {
200         if (&p[1] < art->data + art->len && p[0] == '\r' && p[1] == '\n') {
201             p += 2;
202             *q++ = '\n';
203             i++;
204             if (&p[1] < art->data + art->len && p[0] == '.' && p[1] == '.') {
205                 p += 2;
206                 *q++ = '.';
207                 i++;
208             }
209             if (&p[2] < art->data + art->len && p[0] == '.' && p[1] == '\r' && p[2] == '\n') {
210                 break;
211             }
212         } else {
213             *q++ = *p++;
214             i++;
215         }
216     }
217     *q++ = '\0';
218
219     /* Write the data. */
220     if (Concat) {
221         /* Write a separator... */
222         fprintf(out, "-----------\n");
223     }
224     if (fwrite(article, i, 1, out) != 1) {
225         syswarn("cannot write to %s", dest);
226         fclose(out);
227         if (!Concat) unlink(dest);
228         free(article);
229         return false;
230     }
231     free(article);
232
233     /* Flush and close the output. */
234     if (ferror(out) || fflush(out) == EOF) {
235         syswarn("cannot flush %s", dest);
236         if (!Concat) unlink(dest);
237         fclose(out);
238         return false;
239     }
240     if (fclose(out) == EOF) {
241         syswarn("cannot close %s", dest);
242         if (!Concat) unlink(dest);
243         return false;
244     }
245
246     return true;
247 }
248
249
250 /*
251 **  Write an index entry.  Ignore I/O errors; our caller checks for them.
252 */
253 static void
254 WriteArtIndex(ARTHANDLE *art, char *ShortName)
255 {
256     const char  *p;
257     int i;
258     char                Subject[BUFSIZ];
259     char                MessageID[BUFSIZ];
260
261     Subject[0] = '\0';          /* default to null string */
262     p = wire_findheader(art->data, art->len, "Subject");
263     if (p != NULL) {
264         for (i=0; *p != '\r' && *p != '\n' && *p != '\0'; i++) {
265             Subject[i] = *p++;
266         }
267         Subject[i] = '\0';
268     }
269
270     MessageID[0] = '\0';        /* default to null string */
271     p = wire_findheader(art->data, art->len, "Message-ID");
272     if (p != NULL) {
273         for (i=0; *p != '\r' && *p != '\n' && *p != '\0'; i++) {
274             MessageID[i] = *p++;
275         }
276         MessageID[i] = '\0';
277     }
278
279     printf("%s %s %s\n",
280             ShortName,
281             MessageID[0] ? MessageID : "<none>",
282             Subject[0] ? Subject : "<none>");
283 }
284
285
286 /*
287 ** Crack an Xref line apart into separate strings, each of the form "ng:artnum".
288 ** Return in "lenp" the number of newsgroups found.
289 ** 
290 ** This routine blatantly stolen from tradspool.c
291 */
292 static char **
293 CrackXref(const char *xref, unsigned int *lenp) {
294     char *p;
295     char **xrefs;
296     char *q;
297     unsigned int len, xrefsize;
298
299     len = 0;
300     xrefsize = 5;
301     xrefs = xmalloc(xrefsize * sizeof(char *));
302
303     /* skip pathhost */
304     if ((p = strchr(xref, ' ')) == NULL) {
305         warn("cannot find pathhost in Xref header");
306         return NULL;
307     }
308     /* skip next spaces */
309     for (p++; *p == ' ' ; p++) ;
310     while (true) {
311         /* check for EOL */
312         /* shouldn't ever hit null w/o hitting a \r\n first, but best to be paranoid */
313         if (*p == '\n' || *p == '\r' || *p == 0) {
314             /* hit EOL, return. */
315             *lenp = len;
316             return xrefs;
317         }
318         /* skip to next space or EOL */
319         for (q=p; *q && *q != ' ' && *q != '\n' && *q != '\r' ; ++q) ;
320
321         xrefs[len] = xstrndup(p, q - p);
322
323         if (++len == xrefsize) {
324             /* grow xrefs if needed. */
325             xrefsize *= 2;
326             xrefs = xrealloc(xrefs, xrefsize * sizeof(char *));
327         }
328
329         p = q;
330         /* skip spaces */
331         for ( ; *p == ' ' ; p++) ;
332     }
333 }
334
335
336 /*
337 ** Crack an groups pattern parameter apart into separate strings
338 ** Return in "lenp" the number of patterns found.
339 */
340 static char **
341 CrackGroups(char *group, unsigned int *lenp) {
342     char *p;
343     char **groups;
344     char *q;
345     unsigned int len, grpsize;
346
347     len = 0;
348     grpsize = 5;
349     groups = xmalloc(grpsize * sizeof(char *));
350
351     /* skip leading spaces */
352     for (p=group; *p == ' ' ; p++) ;
353     while (true) {
354         /* check for EOL */
355         /* shouldn't ever hit null w/o hitting a \r\n first, but best to be paranoid */
356         if (*p == '\n' || *p == '\r' || *p == 0) {
357             /* hit EOL, return. */
358             *lenp = len;
359             return groups;
360         }
361         /* skip to next comma, space, or EOL */
362         for (q=p; *q && *q != ',' && *q != ' ' && *q != '\n' && *q != '\r' ; ++q) ;
363
364         groups[len] = xstrndup(p, q - p);
365
366         if (++len == grpsize) {
367             /* grow groups if needed. */
368             grpsize *= 2;
369             groups = xrealloc(groups, grpsize * sizeof(char *));
370         }
371
372         p = q;
373         /* skip commas and spaces */
374         for ( ; *p == ' ' || *p == ',' ; p++) ;
375     }
376 }
377
378
379 int
380 main(int ac, char *av[])
381 {
382     char        *Name;
383     char        *p;
384     FILE        *F;
385     int i;
386     bool                Flat;
387     bool                Redirect;
388     bool                Concat;
389     char                *Index;
390     char                buff[BUFSIZ];
391     char                *spool;
392     char                dest[BUFSIZ];
393     char                **groups, *q, *ng;
394     char                **xrefs;
395     const char          *xrefhdr;
396     ARTHANDLE           *art;
397     TOKEN               token;
398     unsigned int        numgroups, numxrefs;
399     int                 j;
400     char                *base = NULL;
401     bool                doit;
402
403     /* First thing, set up our identity. */
404     message_program_name = "archive";
405
406     /* Set defaults. */
407     if (!innconf_read(NULL))
408         exit(1);
409     Concat = false;
410     Flat = false;
411     Index = NULL;
412     Redirect = true;
413     umask(NEWSUMASK);
414     ERRLOG = concatpath(innconf->pathlog, _PATH_ERRLOG);
415     Archive = innconf->patharchive;
416     groups = NULL;
417     numgroups = 0;
418
419     /* Parse JCL. */
420     while ((i = getopt(ac, av, "a:cfi:p:r")) != EOF)
421         switch (i) {
422         default:
423             die("usage error");
424             break;
425         case 'a':
426             Archive = optarg;
427             break;
428         case 'c':
429             Flat = true;
430             Concat = true;
431             break;
432         case 'f':
433             Flat = true;
434             break;
435         case 'i':
436             Index = optarg;
437             break;
438         case 'p':
439             groups = CrackGroups(optarg, &numgroups);
440             break;
441         case 'r':
442             Redirect = false;
443             break;
444         }
445
446     /* Parse arguments -- at most one, the batchfile. */
447     ac -= optind;
448     av += optind;
449     if (ac > 2)
450         die("usage error");
451
452     /* Do file redirections. */
453     if (Redirect)
454         freopen(ERRLOG, "a", stderr);
455     if (ac == 1 && freopen(av[0], "r", stdin) == NULL)
456         sysdie("cannot open %s for input", av[0]);
457     if (Index && freopen(Index, "a", stdout) == NULL)
458         sysdie("cannot open %s for output", Index);
459
460     /* Go to where the action is. */
461     if (chdir(innconf->patharticles) < 0)
462         sysdie("cannot chdir to %s", innconf->patharticles);
463
464     /* Set up the destination. */
465     strcpy(dest, Archive);
466     Name = dest + strlen(dest);
467     *Name++ = '/';
468
469     if (!SMinit())
470         die("cannot initialize storage manager: %s", SMerrorstr);
471
472     /* Read input. */
473     while (fgets(buff, sizeof buff, stdin) != NULL) {
474         if ((p = strchr(buff, '\n')) == NULL) {
475             warn("skipping %.40s: too long", buff);
476             continue;
477         }
478         *p = '\0';
479         if (buff[0] == '\0' || buff[0] == '#')
480             continue;
481
482         /* Check to see if this is a token... */
483         if (IsToken(buff)) {
484             /* Get a copy of the article. */
485             token = TextToToken(buff);
486             if ((art = SMretrieve(token, RETR_ALL)) == NULL) {
487                 warn("cannot retrieve %s", buff);
488                 continue;
489             }
490
491             /* Determine groups from the Xref header */
492             xrefhdr = wire_findheader(art->data, art->len, "Xref");
493             if (xrefhdr == NULL) {
494                 warn("cannot find Xref header");
495                 SMfreearticle(art);
496                 continue;
497             }
498
499             if ((xrefs = CrackXref(xrefhdr, &numxrefs)) == NULL || numxrefs == 0) {
500                 warn("bogus Xref header");
501                 SMfreearticle(art);
502                 continue;
503             }
504
505             /* Process each newsgroup... */
506             if (base) {
507                 free(base);
508                 base = NULL;
509             }
510             for (i=0; (unsigned)i<numxrefs; i++) {
511                 /* Check for group limits... -p flag */
512                 if ((p=strchr(xrefs[i], ':')) == NULL) {
513                     warn("bogus Xref entry %s", xrefs[i]);
514                     continue;   /* Skip to next xref */
515                 }
516                 if (numgroups > 0) {
517                     *p = '\0';
518                     ng = xrefs[i];
519                     doit = false;
520                     for (j=0; (unsigned)j<numgroups && !doit; j++) {
521                         if (uwildmat(ng, groups[j]) != 0) doit=true;
522                     }
523                 }
524                 else {
525                     doit = true;
526                 }
527                 *p = '/';
528                 if (doit) {
529                     p = Name;
530                     q = xrefs[i];
531                     while(*q) {
532                         *p++ = *q++;
533                     }
534                     *p='\0';
535
536                     if (!Flat) {
537                         for (p=Name; *p; p++) {
538                             if (*p == '.') {
539                                 *p = '/';
540                             }
541                         }
542                     }
543
544                     if (Concat) {
545                         p = strrchr(Name, '/');
546                         q = DateString();
547                         p++;
548                         while (*q) {
549                             *p++ = *q++;
550                         }
551                         *p = '\0';
552                     }
553                         
554                     if (base && !Concat) {
555                         /* Try to link the file into the archive. */
556                         if (link(base, dest) < 0) {
557
558                             /* Make the archive directory. */
559                             if (!MakeArchiveDirectory(dest)) {
560                                 syswarn("cannot mkdir for %s", dest);
561                                 continue;
562                             }
563
564                             /* Try to link again; if that fails, make a copy. */
565                             if (link(base, dest) < 0) {
566 #if     defined(HAVE_SYMLINK)
567                                 if (symlink(base, dest) < 0)
568                                     syswarn("cannot symlink %s to %s",
569                                             dest, base);
570                                 else
571 #endif  /* defined(HAVE_SYMLINK) */
572                                 if (!Copy(base, dest))
573                                     continue;
574                                 continue;
575                             }
576                         }
577                     } else {
578                         if (!CopyArt(art, dest, Concat))
579                             syswarn("copying %s to %s failed", buff, dest);
580                         base = xstrdup(dest);
581                     }
582
583                     /* Write index. */
584                     if (Index) {
585                         WriteArtIndex(art, Name);
586                         if (ferror(stdout) || fflush(stdout) == EOF)
587                             syswarn("cannot write index for %s", Name);
588                     }
589                 }
590             }
591
592             /* Free up the article storage space */
593             SMfreearticle(art);
594             art = NULL;
595             /* Free up the xrefs storage space */
596             for ( i=0; (unsigned)i<numxrefs; i++) free(xrefs[i]);
597             free(xrefs);
598             numxrefs = 0;
599             xrefs = NULL;
600         } else {
601             warn("%s is not a token", buff);
602             continue;
603         }
604     }
605
606     /* close down the storage manager api */
607     SMshutdown();
608
609     /* If we read all our input, try to remove the file, and we're done. */
610     if (feof(stdin)) {
611         fclose(stdin);
612         if (av[0])
613             unlink(av[0]);
614         exit(0);
615     }
616
617     /* Make an appropriate spool file. */
618     p = av[0];
619     if (p == NULL)
620         spool = concatpath(innconf->pathoutgoing, "archive");
621     else if (*p == '/')
622         spool = concat(p, ".bch", (char *) 0);
623     else
624         spool = concat(innconf->pathoutgoing, "/", p, ".bch", (char *) 0);
625     if ((F = xfopena(spool)) == NULL)
626         sysdie("cannot spool to %s", spool);
627
628     /* Write the rest of stdin to the spool file. */
629     i = 0;
630     if (fprintf(F, "%s\n", buff) == EOF) {
631         syswarn("cannot start spool");
632         i = 1;
633     }
634     while (fgets(buff, sizeof buff, stdin) != NULL) 
635         if (fputs(buff, F) == EOF) {
636             syswarn("cannot write to spool");
637             i = 1;
638             break;
639         }
640     if (fclose(F) == EOF) {
641         syswarn("cannot close spool");
642         i = 1;
643     }
644
645     /* If we had a named input file, try to rename the spool. */
646     if (p != NULL && rename(spool, av[0]) < 0) {
647         syswarn("cannot rename spool");
648         i = 1;
649     }
650
651     exit(i);
652     /* NOTREACHED */
653 }