1 /* $Id: archive.c 6138 2003-01-19 04:13:51Z rra $
3 ** Read batchfiles on standard input and archive them.
14 # include <sys/time.h>
17 #include "inn/innconf.h"
18 #include "inn/messages.h"
25 static char *Archive = NULL;
26 static char *ERRLOG = NULL;
29 ** Return a YYYYMM string that represents the current year/month
40 snprintf(ds, sizeof(ds), "%d%d", x->tm_year + 1900, x->tm_mon + 1);
47 ** Try to make one directory. Return false on error.
54 if (mkdir(Name, GROUPDIR_MODE) >= 0)
57 /* See if it failed because it already exists. */
58 return stat(Name, &Sb) >= 0 && S_ISDIR(Sb.st_mode);
63 ** Given an entry, comp/foo/bar/1123, create the directory and all
64 ** parent directories needed. Return false on error.
67 MakeArchiveDirectory(char *Name)
73 if ((save = strrchr(Name, '/')) != NULL)
76 /* Optimize common case -- parent almost always exists. */
83 /* Try to make each of comp and comp/foo in turn. */
84 for (p = Name; *p; p++)
85 if (*p == '/' && p != Name) {
104 ** Copy a file. Return false if error.
107 Copy(char *src, char *dest)
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);
123 out = fopen(dest, "w");
125 if (p == NULL || out == NULL) {
126 syswarn("cannot open %s for writing", dest);
131 /* Opening the input file is easier. */
132 if ((in = fopen(src, "r")) == NULL) {
133 syswarn("cannot open %s for reading", src);
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);
150 /* Flush and close the output. */
151 if (ferror(out) || fflush(out) == EOF) {
152 syswarn("cannot flush %s", dest);
157 if (fclose(out) == EOF) {
158 syswarn("cannot close %s", dest);
168 ** Copy an article from memory into a file.
171 CopyArt(ARTHANDLE *art, char *dest, bool Concat)
177 const char *mode = "w";
179 if (Concat) mode = "a";
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);
189 out = fopen(dest, mode);
191 if (p == NULL || out == NULL) {
192 syswarn("cannot open %s for writing", dest);
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') {
204 if (&p[1] < art->data + art->len && p[0] == '.' && p[1] == '.') {
209 if (&p[2] < art->data + art->len && p[0] == '.' && p[1] == '\r' && p[2] == '\n') {
219 /* Write the data. */
221 /* Write a separator... */
222 fprintf(out, "-----------\n");
224 if (fwrite(article, i, 1, out) != 1) {
225 syswarn("cannot write to %s", dest);
227 if (!Concat) unlink(dest);
233 /* Flush and close the output. */
234 if (ferror(out) || fflush(out) == EOF) {
235 syswarn("cannot flush %s", dest);
236 if (!Concat) unlink(dest);
240 if (fclose(out) == EOF) {
241 syswarn("cannot close %s", dest);
242 if (!Concat) unlink(dest);
251 ** Write an index entry. Ignore I/O errors; our caller checks for them.
254 WriteArtIndex(ARTHANDLE *art, char *ShortName)
258 char Subject[BUFSIZ];
259 char MessageID[BUFSIZ];
261 Subject[0] = '\0'; /* default to null string */
262 p = wire_findheader(art->data, art->len, "Subject");
264 for (i=0; *p != '\r' && *p != '\n' && *p != '\0'; i++) {
270 MessageID[0] = '\0'; /* default to null string */
271 p = wire_findheader(art->data, art->len, "Message-ID");
273 for (i=0; *p != '\r' && *p != '\n' && *p != '\0'; i++) {
281 MessageID[0] ? MessageID : "<none>",
282 Subject[0] ? Subject : "<none>");
287 ** Crack an Xref line apart into separate strings, each of the form "ng:artnum".
288 ** Return in "lenp" the number of newsgroups found.
290 ** This routine blatantly stolen from tradspool.c
293 CrackXref(const char *xref, unsigned int *lenp) {
297 unsigned int len, xrefsize;
301 xrefs = xmalloc(xrefsize * sizeof(char *));
304 if ((p = strchr(xref, ' ')) == NULL) {
305 warn("cannot find pathhost in Xref header");
308 /* skip next spaces */
309 for (p++; *p == ' ' ; p++) ;
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. */
318 /* skip to next space or EOL */
319 for (q=p; *q && *q != ' ' && *q != '\n' && *q != '\r' ; ++q) ;
321 xrefs[len] = xstrndup(p, q - p);
323 if (++len == xrefsize) {
324 /* grow xrefs if needed. */
326 xrefs = xrealloc(xrefs, xrefsize * sizeof(char *));
331 for ( ; *p == ' ' ; p++) ;
337 ** Crack an groups pattern parameter apart into separate strings
338 ** Return in "lenp" the number of patterns found.
341 CrackGroups(char *group, unsigned int *lenp) {
345 unsigned int len, grpsize;
349 groups = xmalloc(grpsize * sizeof(char *));
351 /* skip leading spaces */
352 for (p=group; *p == ' ' ; p++) ;
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. */
361 /* skip to next comma, space, or EOL */
362 for (q=p; *q && *q != ',' && *q != ' ' && *q != '\n' && *q != '\r' ; ++q) ;
364 groups[len] = xstrndup(p, q - p);
366 if (++len == grpsize) {
367 /* grow groups if needed. */
369 groups = xrealloc(groups, grpsize * sizeof(char *));
373 /* skip commas and spaces */
374 for ( ; *p == ' ' || *p == ',' ; p++) ;
380 main(int ac, char *av[])
393 char **groups, *q, *ng;
398 unsigned int numgroups, numxrefs;
403 /* First thing, set up our identity. */
404 message_program_name = "archive";
407 if (!innconf_read(NULL))
414 ERRLOG = concatpath(innconf->pathlog, _PATH_ERRLOG);
415 Archive = innconf->patharchive;
420 while ((i = getopt(ac, av, "a:cfi:p:r")) != EOF)
439 groups = CrackGroups(optarg, &numgroups);
446 /* Parse arguments -- at most one, the batchfile. */
452 /* Do file redirections. */
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);
460 /* Go to where the action is. */
461 if (chdir(innconf->patharticles) < 0)
462 sysdie("cannot chdir to %s", innconf->patharticles);
464 /* Set up the destination. */
465 strcpy(dest, Archive);
466 Name = dest + strlen(dest);
470 die("cannot initialize storage manager: %s", SMerrorstr);
473 while (fgets(buff, sizeof buff, stdin) != NULL) {
474 if ((p = strchr(buff, '\n')) == NULL) {
475 warn("skipping %.40s: too long", buff);
479 if (buff[0] == '\0' || buff[0] == '#')
482 /* Check to see if this is a token... */
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);
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");
499 if ((xrefs = CrackXref(xrefhdr, &numxrefs)) == NULL || numxrefs == 0) {
500 warn("bogus Xref header");
505 /* Process each newsgroup... */
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 */
520 for (j=0; (unsigned)j<numgroups && !doit; j++) {
521 if (uwildmat(ng, groups[j]) != 0) doit=true;
537 for (p=Name; *p; p++) {
545 p = strrchr(Name, '/');
554 if (base && !Concat) {
555 /* Try to link the file into the archive. */
556 if (link(base, dest) < 0) {
558 /* Make the archive directory. */
559 if (!MakeArchiveDirectory(dest)) {
560 syswarn("cannot mkdir for %s", dest);
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",
571 #endif /* defined(HAVE_SYMLINK) */
572 if (!Copy(base, dest))
578 if (!CopyArt(art, dest, Concat))
579 syswarn("copying %s to %s failed", buff, dest);
580 base = xstrdup(dest);
585 WriteArtIndex(art, Name);
586 if (ferror(stdout) || fflush(stdout) == EOF)
587 syswarn("cannot write index for %s", Name);
592 /* Free up the article storage space */
595 /* Free up the xrefs storage space */
596 for ( i=0; (unsigned)i<numxrefs; i++) free(xrefs[i]);
601 warn("%s is not a token", buff);
606 /* close down the storage manager api */
609 /* If we read all our input, try to remove the file, and we're done. */
617 /* Make an appropriate spool file. */
620 spool = concatpath(innconf->pathoutgoing, "archive");
622 spool = concat(p, ".bch", (char *) 0);
624 spool = concat(innconf->pathoutgoing, "/", p, ".bch", (char *) 0);
625 if ((F = xfopena(spool)) == NULL)
626 sysdie("cannot spool to %s", spool);
628 /* Write the rest of stdin to the spool file. */
630 if (fprintf(F, "%s\n", buff) == EOF) {
631 syswarn("cannot start spool");
634 while (fgets(buff, sizeof buff, stdin) != NULL)
635 if (fputs(buff, F) == EOF) {
636 syswarn("cannot write to spool");
640 if (fclose(F) == EOF) {
641 syswarn("cannot close spool");
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");