/* $Id: archive.c 6138 2003-01-19 04:13:51Z rra $ ** ** Read batchfiles on standard input and archive them. */ #include "config.h" #include "clibrary.h" #include #include #include #include #ifdef TM_IN_SYS_TIME # include #endif #include "inn/innconf.h" #include "inn/messages.h" #include "inn/wire.h" #include "libinn.h" #include "paths.h" #include "storage.h" static char *Archive = NULL; static char *ERRLOG = NULL; /* ** Return a YYYYMM string that represents the current year/month */ static char * DateString(void) { static char ds[10]; time_t now; struct tm *x; time(&now); x = localtime(&now); snprintf(ds, sizeof(ds), "%d%d", x->tm_year + 1900, x->tm_mon + 1); return ds; } /* ** Try to make one directory. Return false on error. */ static bool MakeDir(char *Name) { struct stat Sb; if (mkdir(Name, GROUPDIR_MODE) >= 0) return true; /* See if it failed because it already exists. */ return stat(Name, &Sb) >= 0 && S_ISDIR(Sb.st_mode); } /* ** Given an entry, comp/foo/bar/1123, create the directory and all ** parent directories needed. Return false on error. */ static bool MakeArchiveDirectory(char *Name) { char *p; char *save; bool made; if ((save = strrchr(Name, '/')) != NULL) *save = '\0'; /* Optimize common case -- parent almost always exists. */ if (MakeDir(Name)) { if (save) *save = '/'; return true; } /* Try to make each of comp and comp/foo in turn. */ for (p = Name; *p; p++) if (*p == '/' && p != Name) { *p = '\0'; made = MakeDir(Name); *p = '/'; if (!made) { if (save) *save = '/'; return false; } } made = MakeDir(Name); if (save) *save = '/'; return made; } /* ** Copy a file. Return false if error. */ static bool Copy(char *src, char *dest) { FILE *in; FILE *out; size_t i; char *p; char buff[BUFSIZ]; /* Open the output file. */ if ((out = fopen(dest, "w")) == NULL) { /* Failed; make any missing directories and try again. */ if ((p = strrchr(dest, '/')) != NULL) { if (!MakeArchiveDirectory(dest)) { syswarn("cannot mkdir for %s", dest); return false; } out = fopen(dest, "w"); } if (p == NULL || out == NULL) { syswarn("cannot open %s for writing", dest); return false; } } /* Opening the input file is easier. */ if ((in = fopen(src, "r")) == NULL) { syswarn("cannot open %s for reading", src); fclose(out); unlink(dest); return false; } /* Write the data. */ while ((i = fread(buff, 1, sizeof buff, in)) != 0) if (fwrite(buff, 1, i, out) != i) { syswarn("cannot write to %s", dest); fclose(in); fclose(out); unlink(dest); return false; } fclose(in); /* Flush and close the output. */ if (ferror(out) || fflush(out) == EOF) { syswarn("cannot flush %s", dest); unlink(dest); fclose(out); return false; } if (fclose(out) == EOF) { syswarn("cannot close %s", dest); unlink(dest); return false; } return true; } /* ** Copy an article from memory into a file. */ static bool CopyArt(ARTHANDLE *art, char *dest, bool Concat) { FILE *out; const char *p; char *q, *article; size_t i; const char *mode = "w"; if (Concat) mode = "a"; /* Open the output file. */ if ((out = fopen(dest, mode)) == NULL) { /* Failed; make any missing directories and try again. */ if ((p = strrchr(dest, '/')) != NULL) { if (!MakeArchiveDirectory(dest)) { syswarn("cannot mkdir for %s", dest); return false; } out = fopen(dest, mode); } if (p == NULL || out == NULL) { syswarn("cannot open %s for writing", dest); return false; } } /* Copy the data. */ article = xmalloc(art->len); for (i=0, q=article, p=art->data; pdata+art->len;) { if (&p[1] < art->data + art->len && p[0] == '\r' && p[1] == '\n') { p += 2; *q++ = '\n'; i++; if (&p[1] < art->data + art->len && p[0] == '.' && p[1] == '.') { p += 2; *q++ = '.'; i++; } if (&p[2] < art->data + art->len && p[0] == '.' && p[1] == '\r' && p[2] == '\n') { break; } } else { *q++ = *p++; i++; } } *q++ = '\0'; /* Write the data. */ if (Concat) { /* Write a separator... */ fprintf(out, "-----------\n"); } if (fwrite(article, i, 1, out) != 1) { syswarn("cannot write to %s", dest); fclose(out); if (!Concat) unlink(dest); free(article); return false; } free(article); /* Flush and close the output. */ if (ferror(out) || fflush(out) == EOF) { syswarn("cannot flush %s", dest); if (!Concat) unlink(dest); fclose(out); return false; } if (fclose(out) == EOF) { syswarn("cannot close %s", dest); if (!Concat) unlink(dest); return false; } return true; } /* ** Write an index entry. Ignore I/O errors; our caller checks for them. */ static void WriteArtIndex(ARTHANDLE *art, char *ShortName) { const char *p; int i; char Subject[BUFSIZ]; char MessageID[BUFSIZ]; Subject[0] = '\0'; /* default to null string */ p = wire_findheader(art->data, art->len, "Subject"); if (p != NULL) { for (i=0; *p != '\r' && *p != '\n' && *p != '\0'; i++) { Subject[i] = *p++; } Subject[i] = '\0'; } MessageID[0] = '\0'; /* default to null string */ p = wire_findheader(art->data, art->len, "Message-ID"); if (p != NULL) { for (i=0; *p != '\r' && *p != '\n' && *p != '\0'; i++) { MessageID[i] = *p++; } MessageID[i] = '\0'; } printf("%s %s %s\n", ShortName, MessageID[0] ? MessageID : "", Subject[0] ? Subject : ""); } /* ** Crack an Xref line apart into separate strings, each of the form "ng:artnum". ** Return in "lenp" the number of newsgroups found. ** ** This routine blatantly stolen from tradspool.c */ static char ** CrackXref(const char *xref, unsigned int *lenp) { char *p; char **xrefs; char *q; unsigned int len, xrefsize; len = 0; xrefsize = 5; xrefs = xmalloc(xrefsize * sizeof(char *)); /* skip pathhost */ if ((p = strchr(xref, ' ')) == NULL) { warn("cannot find pathhost in Xref header"); return NULL; } /* skip next spaces */ for (p++; *p == ' ' ; p++) ; while (true) { /* check for EOL */ /* shouldn't ever hit null w/o hitting a \r\n first, but best to be paranoid */ if (*p == '\n' || *p == '\r' || *p == 0) { /* hit EOL, return. */ *lenp = len; return xrefs; } /* skip to next space or EOL */ for (q=p; *q && *q != ' ' && *q != '\n' && *q != '\r' ; ++q) ; xrefs[len] = xstrndup(p, q - p); if (++len == xrefsize) { /* grow xrefs if needed. */ xrefsize *= 2; xrefs = xrealloc(xrefs, xrefsize * sizeof(char *)); } p = q; /* skip spaces */ for ( ; *p == ' ' ; p++) ; } } /* ** Crack an groups pattern parameter apart into separate strings ** Return in "lenp" the number of patterns found. */ static char ** CrackGroups(char *group, unsigned int *lenp) { char *p; char **groups; char *q; unsigned int len, grpsize; len = 0; grpsize = 5; groups = xmalloc(grpsize * sizeof(char *)); /* skip leading spaces */ for (p=group; *p == ' ' ; p++) ; while (true) { /* check for EOL */ /* shouldn't ever hit null w/o hitting a \r\n first, but best to be paranoid */ if (*p == '\n' || *p == '\r' || *p == 0) { /* hit EOL, return. */ *lenp = len; return groups; } /* skip to next comma, space, or EOL */ for (q=p; *q && *q != ',' && *q != ' ' && *q != '\n' && *q != '\r' ; ++q) ; groups[len] = xstrndup(p, q - p); if (++len == grpsize) { /* grow groups if needed. */ grpsize *= 2; groups = xrealloc(groups, grpsize * sizeof(char *)); } p = q; /* skip commas and spaces */ for ( ; *p == ' ' || *p == ',' ; p++) ; } } int main(int ac, char *av[]) { char *Name; char *p; FILE *F; int i; bool Flat; bool Redirect; bool Concat; char *Index; char buff[BUFSIZ]; char *spool; char dest[BUFSIZ]; char **groups, *q, *ng; char **xrefs; const char *xrefhdr; ARTHANDLE *art; TOKEN token; unsigned int numgroups, numxrefs; int j; char *base = NULL; bool doit; /* First thing, set up our identity. */ message_program_name = "archive"; /* Set defaults. */ if (!innconf_read(NULL)) exit(1); Concat = false; Flat = false; Index = NULL; Redirect = true; umask(NEWSUMASK); ERRLOG = concatpath(innconf->pathlog, _PATH_ERRLOG); Archive = innconf->patharchive; groups = NULL; numgroups = 0; /* Parse JCL. */ while ((i = getopt(ac, av, "a:cfi:p:r")) != EOF) switch (i) { default: die("usage error"); break; case 'a': Archive = optarg; break; case 'c': Flat = true; Concat = true; break; case 'f': Flat = true; break; case 'i': Index = optarg; break; case 'p': groups = CrackGroups(optarg, &numgroups); break; case 'r': Redirect = false; break; } /* Parse arguments -- at most one, the batchfile. */ ac -= optind; av += optind; if (ac > 2) die("usage error"); /* Do file redirections. */ if (Redirect) freopen(ERRLOG, "a", stderr); if (ac == 1 && freopen(av[0], "r", stdin) == NULL) sysdie("cannot open %s for input", av[0]); if (Index && freopen(Index, "a", stdout) == NULL) sysdie("cannot open %s for output", Index); /* Go to where the action is. */ if (chdir(innconf->patharticles) < 0) sysdie("cannot chdir to %s", innconf->patharticles); /* Set up the destination. */ strcpy(dest, Archive); Name = dest + strlen(dest); *Name++ = '/'; if (!SMinit()) die("cannot initialize storage manager: %s", SMerrorstr); /* Read input. */ while (fgets(buff, sizeof buff, stdin) != NULL) { if ((p = strchr(buff, '\n')) == NULL) { warn("skipping %.40s: too long", buff); continue; } *p = '\0'; if (buff[0] == '\0' || buff[0] == '#') continue; /* Check to see if this is a token... */ if (IsToken(buff)) { /* Get a copy of the article. */ token = TextToToken(buff); if ((art = SMretrieve(token, RETR_ALL)) == NULL) { warn("cannot retrieve %s", buff); continue; } /* Determine groups from the Xref header */ xrefhdr = wire_findheader(art->data, art->len, "Xref"); if (xrefhdr == NULL) { warn("cannot find Xref header"); SMfreearticle(art); continue; } if ((xrefs = CrackXref(xrefhdr, &numxrefs)) == NULL || numxrefs == 0) { warn("bogus Xref header"); SMfreearticle(art); continue; } /* Process each newsgroup... */ if (base) { free(base); base = NULL; } for (i=0; (unsigned)i 0) { *p = '\0'; ng = xrefs[i]; doit = false; for (j=0; (unsigned)jpathoutgoing, "archive"); else if (*p == '/') spool = concat(p, ".bch", (char *) 0); else spool = concat(innconf->pathoutgoing, "/", p, ".bch", (char *) 0); if ((F = xfopena(spool)) == NULL) sysdie("cannot spool to %s", spool); /* Write the rest of stdin to the spool file. */ i = 0; if (fprintf(F, "%s\n", buff) == EOF) { syswarn("cannot start spool"); i = 1; } while (fgets(buff, sizeof buff, stdin) != NULL) if (fputs(buff, F) == EOF) { syswarn("cannot write to spool"); i = 1; break; } if (fclose(F) == EOF) { syswarn("cannot close spool"); i = 1; } /* If we had a named input file, try to rename the spool. */ if (p != NULL && rename(spool, av[0]) < 0) { syswarn("cannot rename spool"); i = 1; } exit(i); /* NOTREACHED */ }