home *** CD-ROM | disk | FTP | other *** search
/ Unix System Administration Handbook 1997 October / usah_oct97.iso / news / cnews.tar / expire / expire.c < prev    next >
C/C++ Source or Header  |  1994-11-27  |  31KB  |  1,397 lines

  1. /*
  2.  * expire - expire old news
  3.  *
  4.  * One modest flaw:  links are not preserved in archived copies, i.e. you
  5.  * get multiple copies of multiply-posted articles.  Since link preservation
  6.  * is arbitrarily hard when control files get complex, to hell with it.
  7.  */
  8.  
  9. #include <stdlib.h>
  10. #include <stdio.h>
  11. #include <ctype.h>
  12. #include <string.h>
  13. #include <errno.h>
  14. #include "fixerrno.h"
  15. #include <time.h>
  16. #include <signal.h>
  17. #include <sys/types.h>
  18. #include <sys/timeb.h>
  19. #include <sys/stat.h>
  20. #include "libc.h"
  21. #include "news.h"
  22. #include "config.h"
  23. #include "fgetfln.h"
  24. #include "case.h"
  25. #include "dbz.h"
  26. #include "ngmatch.h"
  27.  
  28. /* basic parameters of time, used in back() (and debugging code) */
  29. #ifndef EPOCH
  30. #define    EPOCH    ((time_t)0)    /* the origin of time_t */
  31. #endif
  32. #ifndef FOREVER
  33. #define    FOREVER    1e37        /* FOREVER > max value of time_t */
  34. #endif
  35. #define    DAY    ((double)24*60*60)
  36.  
  37. /* structure for expiry-control records */
  38. struct ctl {
  39.     struct ctl *next;
  40.     char *groups;        /* newsgroups */
  41.     NGPAT *pat;        /* parsed "groups", if actually a pattern */
  42.     int ismod;        /* moderated? */
  43. #        define    UNMOD    'u'
  44. #        define    MOD    'm'
  45. #        define    EITHER    'x'
  46.     time_t retain;        /* earliest arrival date not expired */
  47.     time_t normal;        /* earliest not expired in default case */
  48.     time_t purge;        /* latest arrival date always expired */
  49.     char *dir;        /* Archive dir or NULL. */
  50.     long ngroups;        /* for explist, number of groups dealt with */
  51.     int lineno;        /* line number in explist */
  52. };
  53.  
  54. /* header for internal form of control file */
  55. struct ctl *ctls = NULL;
  56. struct ctl *lastctl = NULL;
  57.  
  58. /*
  59.  * Headers for lists by newsgroup, derived (mostly) from active file.
  60.  * Hashing is by length of newsgroup name; this is quick and works well,
  61.  * and there is no simple variation that does much better.  (Actually,
  62.  * that statement is obsolete; there are now too many newsgroups for
  63.  * this to work particularly well, although it's not a major profiling
  64.  * hot spot.  Improvements are planned.)
  65.  */
  66. #define    NHASH    80
  67. struct ctl *ngs[NHASH] = { NULL };
  68.  
  69. struct ctl *holdover = NULL;    /* "/expired/" control record */
  70. struct ctl *bounds = NULL;    /* "/bounds/" control record */
  71.  
  72. int debug = 0;            /* for inews routines */
  73. int expdebug = 0;        /* expire debugging */
  74.  
  75. int printexpiring = 0;        /* print info line for expiring articles? */
  76. char *defarch = NULL;        /* default archive dir */
  77. int spacetight = 0;        /* error-recovery actions remove evidence? */
  78.  
  79. char *subsep = "~";        /* subfield separator in middle field */
  80. int checkonly = 0;        /* check control information only */
  81. int testing = 0;        /* testing only, leave articles alone */
  82. int leaders = 0;        /* only first link ("leader") is hard link */
  83. int verbose = 0;        /* report statistics */
  84. int rebuild = 1;        /* rebuild history files */
  85. int holdarch = 0;        /* hold rather than archiving */
  86. int dategrump = 0;        /* report incomprehensible expiry dates */
  87. char *histdir = NULL;        /* where to find history files */
  88.  
  89. long nkept = 0;            /* count of articles not expired */
  90. long ngone = 0;            /* count of articles removed (no links left) */
  91. long nresid = 0;        /* count of residual entries kept */
  92. long narched = 0;        /* count of links archived */
  93. long njunked = 0;        /* count of links just removed */
  94. long nmissing = 0;        /* count of links missing at cp/rm time */
  95.  
  96. long ncpfail = 0;        /* count of failed cp()s */
  97.  
  98. char dont[] = "don't";        /* magic cookie for whereexpire() return */
  99.  
  100. time_t now;            /* set once in startup, as reference */
  101. #define    NODATE    ((time_t)(-1))    /* time_t value indicating date not given */
  102. time_t latest =    0;        /* most recent arrival date */
  103.  
  104. char subject[200] = "";        /* Subject line for -p, minus header */
  105.  
  106. /* Buffer etc. for readline and friends. */
  107. #ifdef SMALLMEM
  108. #define    RLBSIZ    (BUFSIZ)
  109. #else
  110. #define    RLBSIZ    (BUFSIZ*8)    /* we're going to read a biiiig file */
  111. #endif
  112. char rlbuf[RLBSIZ+1];        /* +1 for sentinel */
  113. size_t rlbufsiz = RLBSIZ;
  114. int rlnleft = 0;
  115. char *rest = rlbuf;        /* relies on rlbuf initialized to '\0's */
  116. int nlocked = 0;        /* has readline() locked the news system? */
  117. char *rlline = NULL;        /* malloced when we need to copy a line */
  118. size_t rllsiz = 200;        /* good initial size */
  119.  
  120. /*
  121.  * Archive-copying buffer.
  122.  * 8KB buffer is large enough to take most articles at one gulp,
  123.  * and also large enough for virtual certainty of getting the
  124.  * Subject: line in the first bufferload.
  125.  */
  126. #ifdef SMALLMEM
  127. char abuf[2*1024];        /* expire reported to be tight on 11 */
  128. #else
  129. char abuf[8*1024];
  130. #endif
  131.  
  132. char *progname;
  133.  
  134. extern struct tm *gmtime();
  135. extern time_t time();
  136.  
  137. extern time_t getindate();
  138.  
  139. /* forwards */
  140. FILE *eufopen();
  141. void eufclose();
  142. char *whereexpire();
  143. time_t back();
  144. void checkadir();
  145. void fail();
  146. void die();
  147. void control();
  148. void prime();
  149. void checkused();
  150. void doit();
  151. void cd();
  152. time_t readdate();
  153. char *doarticle();
  154. void warning();
  155. void complain();
  156. void printstuff();
  157. int expire();
  158. char *readline();
  159. void mkparents();
  160. void getsubj();
  161. void refill();
  162. void printlists();
  163. void pctl();
  164. void fillin();
  165. void ctlline();
  166.  
  167. /*
  168.  - main - parse arguments and handle options
  169.  */
  170. main(argc, argv)
  171. int argc;
  172. char *argv[];
  173. {
  174.     register int c;
  175.     register int errflg = 0;
  176.     register FILE *cf;
  177.     extern int optind;
  178.     extern char *optarg;
  179.  
  180.     progname = argv[0];
  181.     now = time((time_t *)NULL);
  182.  
  183.     while ((c = getopt(argc, argv, "pa:sF:cn:tlvrhgH:B:d")) != EOF)
  184.         switch (c) {
  185.         case 'p':    /* print info line for archived articles */
  186.             printexpiring = 1;
  187.             break;
  188.         case 'a':    /* archive in this directory */
  189.             defarch = optarg;
  190.             break;
  191.         case 's':    /* maximize space during error recovery */
  192.             spacetight = 1;
  193.             break;
  194.         case 'F':    /* subfield separator in middle field */
  195.             subsep = optarg;
  196.             break;
  197.         case 'c':    /* check control-file format only */
  198.             checkonly = 1;
  199.             break;
  200.         case 'n':    /* set value of "now" for testing */
  201.             now = atol(optarg);
  202.             break;
  203.         case 't':    /* testing, do not mess with articles */
  204.             testing = 1;
  205.             break;
  206.         case 'l':    /* leaders */
  207.             leaders = 1;
  208.             break;
  209.         case 'v':    /* verbose -- report some statistics */
  210.             verbose = 1;
  211.             break;
  212.         case 'r':    /* suppress history-file rebuild */
  213.             rebuild = 0;
  214.             break;
  215.         case 'h':    /* hold all files meant to be archived */
  216.             holdarch = 1;
  217.             break;
  218.         case 'g':    /* report incomprehensible expiry dates */
  219.             dategrump = 1;
  220.             break;
  221.         case 'H':    /* where to find history files */
  222.             histdir = optarg;
  223.             break;
  224.         case 'B':    /* internal buffer size */
  225.             rlbufsiz = atoi(optarg);
  226.             break;
  227.         case 'd':    /* debug */
  228.             expdebug = 1;
  229.             break;
  230.         case '?':
  231.         default:
  232.             errflg++;
  233.             break;
  234.         }
  235.     if (errflg || optind < argc-1) {
  236.         fprintf(stderr, "Usage: %s [-p] [-s] [-c] [-a archdir] [ctlfile]\n",
  237.                                 progname);
  238.         exit(2);
  239.     }
  240.     if (expdebug)
  241.         setbuf(stderr, (char *)NULL);
  242.  
  243.     if (optind < argc) {
  244.         cf = eufopen(argv[optind], "r");
  245.         control(cf);
  246.         (void) fclose(cf);
  247.     } else
  248.         control(stdin);
  249.     prime(ctlfile("active"));
  250.  
  251.     if (expdebug)
  252.         printlists();
  253.     if (histdir == NULL)
  254.         histdir = strsave(ctlfile((char *)NULL));
  255.     if (defarch != NULL)
  256.         checkadir(defarch);
  257.     if (checkonly)
  258.         exit(0);
  259.  
  260.  
  261.     (void) umask(newsumask());
  262.     doit();            /* side effect: newslock() */
  263.     newsunlock();
  264.  
  265.     if (latest > time((time_t *)NULL)) {
  266.         complain("some article arrival dates are in the future!", "");
  267.         complain("\tis your system clock set wrong?", "");
  268.     }
  269.  
  270.     if (verbose) {
  271.         fprintf(stderr, "%ld kept, %ld expired\n", nkept, ngone);
  272.         fprintf(stderr, "%ld residual lines\n", nresid);
  273.         fprintf(stderr, "%ld links archived, %ld junked, %ld missing\n",
  274.                         narched, njunked, nmissing);
  275.     }
  276.     exit(0);
  277. }
  278.  
  279. /*
  280.  - control - pick up a control file
  281.  */
  282. void
  283. control(f)
  284. register FILE *f;
  285. {
  286.     register char *p;
  287.     register int gotone = 0;
  288.     register int lineno = 0;
  289.  
  290.     while ((p = fgetline(f, (size_t *)NULL)) != NULL) {
  291.         lineno++;
  292.         if (*p != '#')
  293.             ctlline(p, lineno);
  294.         gotone = 1;
  295.     }
  296.  
  297.     if (!gotone)
  298.         die("control file empty!", "");
  299. }
  300.  
  301. /*
  302.  - ctlline - process one control-file line
  303.  */
  304. void
  305. ctlline(ctl, lineno)
  306. char *ctl;
  307. int lineno;
  308. {
  309.     register struct ctl *ct;
  310.     char *field[4];
  311.     char datebuf[50];
  312.     char *dates[3];
  313.     register int nf;
  314.     int ndates;
  315.  
  316.     nf = split(ctl, field, 4, "");
  317.     if (nf == 0)
  318.         return;        /* blank line */
  319.     if (nf != 4)
  320.         die("control line for `%s' hasn't got 4 fields", field[0]);
  321.  
  322.     ct = (struct ctl *)malloc(sizeof(struct ctl));
  323.     if (ct == NULL)
  324.         fail("out of memory for control list", "");
  325.  
  326.     ct->groups = strsave(field[0]);
  327.     ct->pat = ngparse(strsave(ct->groups));
  328.     if (ct->pat == NULL)
  329.         die("can't parse control file pattern `%s'", ct->groups);
  330.     if (STREQ(field[1], "m"))
  331.         ct->ismod = MOD;
  332.     else if (STREQ(field[1], "u"))
  333.         ct->ismod = UNMOD;
  334.     else if (STREQ(field[1], "x"))
  335.         ct->ismod = EITHER;
  336.     else
  337.         die("strange mod field `%s' in control file", field[1]);
  338.  
  339.     if (strlen(field[2]) > sizeof(datebuf)-1)
  340.         die("date specification `%s' too long", field[2]);
  341.     (void) strcpy(datebuf, field[2]);
  342.     ndates = split(datebuf, dates, 3, "-");
  343.     switch (ndates) {
  344.     case 3:
  345.         ct->retain = back(dates[0]);
  346.         ct->normal = back(dates[1]);
  347.         ct->purge = back(dates[2]);
  348.         break;
  349.     case 2:
  350.         ct->retain = (bounds != NULL) ? bounds->retain : back("0");
  351.         ct->normal = back(dates[0]);
  352.         ct->purge = back(dates[1]);
  353.         break;
  354.     case 1:
  355.         ct->retain = (bounds != NULL) ? bounds->retain : back("0");
  356.         ct->normal = back(dates[0]);
  357.         ct->purge = (bounds != NULL) ? bounds->purge : back("never");
  358.         break;
  359.     default:
  360.         die("invalid date specification `%s'", field[2]);
  361.         /* NOTREACHED */
  362.         break;
  363.     }
  364.     if (ct->retain < ct->normal && ndates <= 2)    /* stretch defaults */
  365.         ct->retain = ct->normal;
  366.     if (ct->normal < ct->purge && ndates == 1)
  367.         ct->purge = ct->normal;
  368.  
  369.     if (ct->retain < ct->normal || ct->normal < ct->purge)
  370.         die("preposterous dates: `%s'", field[2]);
  371.  
  372.     if (STREQ(field[3], "-"))
  373.         ct->dir = NULL;
  374.     else if (STREQ(field[3], "@")) {
  375.         if (defarch == NULL)
  376.             die("@ in control file but no -a", "");
  377.         ct->dir = defarch;
  378.     } else {
  379.         ct->dir = strsave(field[3]);
  380.         checkadir(ct->dir);
  381.     }
  382.  
  383.     ct->ngroups = 0;
  384.     ct->lineno = lineno;
  385.  
  386.     /* put it where it belongs */
  387.     if (STREQ(ct->groups, "/expired/"))
  388.         holdover = ct;
  389.     else if (STREQ(ct->groups, "/bounds/"))
  390.         bounds = ct;
  391.     else if (ct->groups[0] == '/')
  392.         die("unknown special line name `%s'", ct->groups);
  393.     else {
  394.         ct->next = NULL;
  395.         if (lastctl == NULL)
  396.             ctls = ct;
  397.         else
  398.             lastctl->next = ct;
  399.         lastctl = ct;
  400.     }
  401. }
  402.  
  403. /*
  404.  - prime - prime control lists from active file
  405.  */
  406. void
  407. prime(afile)
  408. char *afile;
  409. {
  410.     register char *line;
  411.     register FILE *af;
  412.     register struct ctl *ct;
  413. #    define    NFACT    4
  414.     char *field[NFACT];
  415.     int nf;
  416.     register int hash;
  417.  
  418.     af = eufopen(afile, "r");
  419.     while ((line = fgetline(af, (size_t *)NULL)) != NULL) {
  420.         nf = split(line, field, NFACT, "");
  421.         if (nf != NFACT)
  422.             die("wrong number of fields in active for `%s'", field[0]);
  423.         ct = (struct ctl *)malloc(sizeof(struct ctl));
  424.         if (ct == NULL)
  425.             fail("out of memory at newsgroup `%s'", field[0]);
  426.         ct->groups = strsave(field[0]);
  427.         ct->ismod = (strchr(field[3], 'm') != NULL) ? MOD : UNMOD;
  428.         fillin(ct);
  429.         hash = strlen(field[0]);
  430.         if (hash > NHASH-1)
  431.             hash = NHASH-1;
  432.         ct->next = ngs[hash];
  433.         ngs[hash] = ct;
  434.     }
  435.     (void) fclose(af);
  436.  
  437.     checkused();
  438. }
  439.  
  440. /*
  441.  - fillin - fill in a ctl struct for a newsgroup from the control-file list
  442.  */
  443. void
  444. fillin(ct)
  445. register struct ctl *ct;
  446. {
  447.     register struct ctl *cscan;
  448.     char grump[100];
  449.  
  450.     for (cscan = ctls; cscan != NULL; cscan = cscan->next)
  451.         if ((cscan->ismod == ct->ismod || cscan->ismod == EITHER) &&
  452.             ngpatmat(cscan->pat, ct->groups)) {
  453.             ct->retain = cscan->retain;
  454.             ct->normal = cscan->normal;
  455.             ct->purge = cscan->purge;
  456.             ct->dir = cscan->dir;
  457.             cscan->ngroups++;
  458.             return;
  459.         }
  460.  
  461.     /* oooooops... */
  462.     sprintf(grump, "group `%%s' (%smoderated) not covered by control file",
  463.                     (ct->ismod == MOD) ? "" : "un");
  464.     die(grump, ct->groups);
  465. }
  466.  
  467. /*
  468.  - checkused - check that all lines of the control file got used
  469.  */
  470. void
  471. checkused()
  472. {
  473.     register struct ctl *cscan;
  474.     char grump[100];
  475.  
  476.     for (cscan = ctls; cscan != NULL; cscan = cscan->next)
  477.         if (cscan->ngroups == 0) {
  478.             sprintf(grump,
  479.     "warning: line %d of control file controls no active newsgroups",
  480.                                 cscan->lineno);
  481.             complain(grump, "");
  482.         }
  483. }
  484.  
  485. /*
  486.  - doit - file manipulation and master control
  487.  */
  488. void
  489. doit()
  490. {
  491.     register int old;
  492.     register FILE *new;
  493.     extern void mainloop();
  494.  
  495.     cd(histdir);
  496.     old = open("history", 0);
  497.     if (old < 0)
  498.         fail("cannot open `%s'", "history");
  499.     if (rebuild) {
  500.         (void) remove("history.n");
  501.         (void) remove("history.n.dir");
  502.         (void) remove("history.n.pag");
  503.         if (spacetight)
  504.             (void) remove("history.o");
  505.         new = eufopen("history.n", "w");
  506.         (void) fclose(eufopen("history.n.dir", "w"));
  507.         (void) fclose(eufopen("history.n.pag", "w"));
  508.         (void) dbzincore(1);
  509.         errno = 0;
  510.         if (dbzagain("history.n", "history") < 0)
  511.             fail("dbzagain(history.n) failed", "");
  512.     }
  513.  
  514.     cd(artfile((char *)NULL));
  515.     mainloop(old, new);
  516.     /* side effect of mainloop():  newslock() */
  517.  
  518.     (void) close(old);
  519.     if (rebuild) {
  520.         eufclose(new, "history.n");
  521.         if (dbmclose() < 0)
  522.             fail("dbmclose() failed", "");
  523.     }
  524.  
  525.     if (testing)
  526.         return;
  527.     if (rebuild) {
  528.         cd(histdir);
  529.         if (rename("history", "history.o") != 0)
  530.             fail("can't move history", "");
  531.         if (rename("history.n", "history") != 0)
  532.             fail("disaster -- can't reinstate history!", "");
  533.         if (rename("history.n.dir", "history.dir") != 0)
  534.             fail("disaster -- can't reinstate history.dir!", "");
  535.         if (rename("history.n.pag", "history.pag") != 0)
  536.             fail("disaster -- can't reinstate history.pag!", "");
  537.     }
  538. }
  539.  
  540. /*
  541.  - mainloop - main loop, reading old history file and building new one
  542.  */
  543. void
  544. mainloop(old, new)
  545. register int old;
  546. register FILE *new;
  547. {
  548.     register char *line;
  549.     long here;
  550.     datum lhs;
  551.     datum rhs;
  552.     register int ret;
  553. #    define    NF    3
  554.     char *field[NF];    /* fields in line */
  555.     register int nf;
  556. #    define    NSF    3    /* lump subfields after second into one */
  557.     char *subfield[NSF];    /* subfields in middle field */
  558.     register long lineno = 0;
  559.     char linenobuf[20];
  560.     register int nsf;
  561.     register int i;
  562.  
  563.     while ((line = readline(old)) != NULL) {
  564.         lineno++;
  565.         if (expdebug) {
  566.             fprintf(stderr, "\nline %ld `", lineno);
  567.             fputs(line, stderr);
  568.             fputs("'\n", stderr);
  569.         }
  570.         nf = split(line, field, NF, "\t");
  571.         if (nf == 2)
  572.             field[2] = NULL;
  573.  
  574.         if (nf < 2 || nf > 3 || *field[0] == '\0') {
  575.             sprintf(linenobuf, "%ld", lineno);
  576.             complain("garbled history entry, line %s", linenobuf);
  577.             ret = 0;
  578.         } else {
  579.             nsf = split(field[1], subfield, NSF, subsep);
  580.             if (nsf > NSF)
  581.                 nsf = NSF;
  582.             ret = doline(field, nf, subfield, nsf);
  583.         }
  584.  
  585.         if (ret >= 0 && rebuild) {
  586.             /* make the DBM entry */
  587.             lhs.dptr = field[0];
  588.             lhs.dsize = strlen(field[0])+1;
  589.             here = ftell(new);
  590.             rhs.dptr = (char *)&here;
  591.             rhs.dsize = sizeof(here);
  592.             ret = dbzstore(lhs, rhs);
  593.             if (ret < 0)
  594.                 die("dbzstore failure on `%s'", field[0]);
  595.  
  596.             /* make the history entry */
  597.             fputs(field[0], new);
  598.             putc('\t', new);
  599.             fputs(subfield[0], new);
  600.             for (i = 1; i < nsf; i++) {
  601.                 putc(*subsep, new);
  602.                 fputs(subfield[i], new);
  603.             }
  604.             if (field[2] != NULL) {
  605.                 putc('\t', new);
  606.                 fputs(field[2], new);
  607.             }
  608.             putc('\n', new);
  609.  
  610.             if (expdebug)
  611.                 fprintf(stderr, "new line `%s\t%s%c%s\t%s'\n",
  612.                     field[0], subfield[0], *subsep,
  613.                     subfield[1],
  614.                     (field[2] == NULL) ? "" : field[2]);
  615.         }
  616.     }
  617.     /* side effect of readline() == NULL:  newslock() */
  618. }
  619.  
  620. /*
  621.  - doline - handle one history line, possibly modifying it
  622.  */
  623. int                /* 0 keep it, <0 don't */
  624. doline(field, nf, subfield, nsf)
  625. char *field[NF];        /* fields in line */
  626. register int nf;
  627. char *subfield[NSF];        /* subfields in middle field */
  628. register int nsf;
  629. {
  630.     register time_t recdate;
  631.     register time_t expdate;
  632.     static char expbuf[25];        /* plenty for decimal time_t */
  633.     register char *oldf2;
  634.     register char *sf1 = subfield[1];
  635.  
  636.     /* sort out the dates */
  637.     if (nsf < 2 || *sf1 == '\0' || (*sf1 == '-' && *(sf1+1) == '\0'))
  638.         expdate = NODATE;
  639.     else {
  640.         expdate = readdate(sf1);
  641.         if (expdate == NODATE && dategrump)
  642.             complain("ignoring bad expiry date `%s',", sf1);
  643.     }
  644.     recdate = readdate(subfield[0]);
  645.     if (recdate == NODATE) {
  646.         complain("bad arrival date `%s' -- expiring", subfield[0]);
  647.         recdate = readdate("0");
  648.         expdate = recdate;
  649.     }
  650.     if (recdate > latest)
  651.         latest = recdate;
  652.     if (expdebug)
  653.         fprintf(stderr, "rec %ld, expire %ld\n", (long)recdate,
  654.                                 (long)expdate);
  655.  
  656.     /* deal with it */
  657.     oldf2 = field[2];
  658.     field[2] = doarticle(oldf2, recdate, expdate, field[0]);
  659.     if (oldf2 != NULL) {
  660.         if (field[2] == NULL)
  661.             ngone++;
  662.         else
  663.             nkept++;
  664.     }
  665.     if (field[2] == NULL) {
  666.         if (holdover == NULL || shouldgo(recdate, NODATE, holdover))
  667.             return(-1);    /* easy case -- get rid of it */
  668.         nresid++;
  669.     }
  670.  
  671.     /* hard case -- must rebuild expiry date */
  672.     if (expdate != NODATE) {
  673.         sprintf(expbuf, "%ld", (long)expdate);
  674.         subfield[1] = expbuf;
  675.     } else
  676.         subfield[1] = "-";
  677.     return(0);
  678. }
  679.  
  680. /*
  681.  - readdate - turn a date into internal form
  682.  */
  683. time_t
  684. readdate(text)
  685. char *text;
  686. {
  687.     register time_t ret;
  688.  
  689.     /* heuristic:  non-numeric dates never involve numbers >4 digits */
  690.     ret = atol(text);
  691.     if (ret < 10000 && strspn(text, "0123456789") != strlen(text)) {
  692.         ret = getindate(text, (struct timeb *)NULL);
  693.         if (ret == -1)
  694.             ret = NODATE;
  695.     }
  696.  
  697.     return(ret);
  698. }
  699.  
  700. /*
  701.  - doarticle - possibly expire an article
  702.  *
  703.  * Re-uses the space of its first argument.
  704.  */
  705. char *                /* new name list, in space of old, or NULL */
  706. doarticle(oldnames, recdate, expdate, msgid)
  707. char *oldnames;            /* may be destroyed */
  708. time_t recdate;
  709. time_t expdate;
  710. char *msgid;            /* for printstuff() */
  711. {
  712.     register char *src;
  713.     register char *dst;
  714.     register char *name;
  715.     register char *dir;
  716.     register char *p;
  717.     register char srcc;
  718.     register int nleft;
  719.     register int nexpired;
  720.     register int ret;
  721. #    define    NDELIM    " ,"
  722.  
  723.     if (oldnames == NULL)
  724.         return(NULL);
  725.  
  726.     src = oldnames;
  727.     dst = oldnames;
  728.     nleft = 0;
  729.     nexpired = 0;
  730.     for (;;) {
  731.         src += strspn(src, NDELIM);
  732.         name = src;
  733.         src += strcspn(src, NDELIM);
  734.         srcc = *src;
  735.         *src = '\0';
  736.         if (*name == '\0')
  737.             break;        /* NOTE BREAK OUT */
  738.         if (expdebug)
  739.             fprintf(stderr, "name `%s'\n", name);
  740.  
  741.         ret = -1;
  742.         dir = whereexpire(recdate, expdate, name);
  743.         if (dir != dont && !(leaders && nleft == 0 && srcc != '\0')) {
  744.             if (expdebug)
  745.                 fprintf(stderr, "expire into `%s'\n",
  746.                     (dir == NULL) ? "(null)" : dir);
  747.             for (p = strchr(name, '.'); p != NULL;
  748.                             p = strchr(p+1, '.'))
  749.                 *p = '/';
  750.             ret = expire(name, dir);
  751.             if (ret < 0) {    /* oops -- couldn't expire it! */
  752.                 for (p = strchr(name, '/'); p != NULL;
  753.                             p = strchr(p+1, '/'))
  754.                     *p = '.';
  755.                 p = strrchr(name, '.');
  756.                 if (p != NULL)
  757.                     *p = '/';
  758.             }
  759.         }
  760.         if (ret > 0) {        /* we got rid of it */
  761.             if (dir != NULL && printexpiring)
  762.                 printstuff(msgid, name, recdate);
  763.             nexpired++;
  764.         } else if (ret < 0) {    /* it's still around */
  765.             if (dst != oldnames)
  766.                 *dst++ = ' ';
  767.             while (*name != '\0')
  768.                 *dst++ = *name++;
  769.             nleft++;
  770.         }
  771.         *src = srcc;
  772.     }
  773.  
  774.     if (nleft == 0)
  775.         return(NULL);
  776.     *dst++ = '\0';
  777.     if (leaders && nleft == 1 && nexpired > 0)    /* aging leader */
  778.         return(doarticle(oldnames, recdate, expdate, msgid));
  779.     return(oldnames);
  780. }
  781.  
  782. /*
  783.  - whereexpire - where should this name expire to, and should it?
  784.  *
  785.  * The "dont" variable's address is used as the don't-expire return value,
  786.  * since NULL means "to nowhere".
  787.  */
  788. char *                /* archive directory, NULL, or dont */
  789. whereexpire(recdate, expdate, name)
  790. time_t recdate;
  791. time_t expdate;
  792. char *name;
  793. {
  794.     register char *group;
  795.     register char *slash;
  796.     register struct ctl *ct;
  797.     register int hash;
  798.  
  799.     group = name;
  800.     slash = strchr(group, '/');
  801.     if (slash == NULL)
  802.         die("no slash in article path `%s'", name);
  803.     else
  804.         *slash = '\0';
  805.     if (strchr(slash+1, '/') != NULL) {
  806.         *slash = '/';
  807.         die("multiple slashes in article path `%s'", name);
  808.     }
  809.  
  810.     /* find applicable expiry-control struct (make it if necessary) */
  811.     hash = strlen(group);
  812.     if (hash > NHASH-1)
  813.         hash = NHASH-1;
  814.     for (ct = ngs[hash]; ct != NULL && !STREQ(ct->groups, group);
  815.                                 ct = ct->next)
  816.         continue;
  817.     if (ct == NULL) {    /* oops, there wasn't one */
  818.         if (expdebug)
  819.             fprintf(stderr, "new group `%s'\n", group);
  820.         ct = (struct ctl *)malloc(sizeof(struct ctl));
  821.         if (ct == NULL)
  822.             fail("out of memory for newsgroup `%s'", group);
  823.         ct->groups = strsave(group);
  824.         ct->ismod = UNMOD;    /* unknown -- treat it as mundane */
  825.         fillin(ct);
  826.         ct->next = ngs[hash];
  827.         ngs[hash] = ct;
  828.     }
  829.     *slash = '/';
  830.  
  831.     /* and decide */
  832.     if (!shouldgo(recdate, expdate, ct) || (holdarch && ct->dir != NULL))
  833.         return(dont);
  834.     else
  835.         return(ct->dir);
  836. }
  837.  
  838. /*
  839.  - shouldgo - should article with these dates expire now?
  840.  */
  841. int                /* predicate */
  842. shouldgo(recdate, expdate, ct)
  843. time_t recdate;
  844. time_t expdate;
  845. register struct ctl *ct;
  846. {
  847.     if (recdate >= ct->retain)    /* within retention period */
  848.         return(0);
  849.     if (recdate <= ct->purge)    /* past purge date */
  850.         return(1);
  851.     if (expdate != NODATE) {
  852.         if (now >= expdate)    /* past its explicit date */
  853.             return(1);
  854.         else
  855.             return(0);
  856.     } else {
  857.         if (recdate < ct->normal)    /* past default date */
  858.             return(1);
  859.         else
  860.             return(0);
  861.     }
  862.     /* NOTREACHED */
  863. }
  864.  
  865. /*
  866.  - expire - expire an article
  867.  */
  868. int                /* >0 success, 0 missing, <0 failure */
  869. expire(name, dir)
  870. char *name;
  871. char *dir;
  872. {
  873.     register char *old;
  874.     register char *new;
  875.     register int ret;
  876.  
  877.     if (testing) {
  878.         if (dir != NULL)
  879.             fprintf(stderr, "copy %s %s ; ", name, dir);
  880.         fprintf(stderr, "remove %s\n", name);
  881.         return(1);
  882.     }
  883.  
  884.     if (dir != NULL) {        /* should archive */
  885.         /* sort out filenames */
  886.         old = strsave(artfile(name));
  887.         if (*dir == '=') {
  888.             new = strrchr(name, '/');
  889.             if (new == NULL)
  890.                 die("no slash in `%s'", name);
  891.             new++;
  892.             new = str3save(dir+1, "/", new);
  893.         } else
  894.             new = str3save(dir, "/", name);
  895.  
  896.         /* cp() usually succeeds, so try it before getting fancy */
  897.         ret = cp(old, new);
  898.         if (ret == 'n')        /* new could not be created */
  899.             if (*dir != '=') {
  900.                 mkparents(name, dir);
  901.                 ret = cp(old, new);    /* try again */
  902.             }
  903.         switch (ret) {        /* report problems, if any */
  904.         case 0:            /* success */
  905.             narched++;
  906.             break;
  907.         case 'o':        /* old did not exist */
  908.             nmissing++;
  909.             ret = 0;    /* not strictly an error */
  910.             break;
  911.         case 'n':        /* new could not be created */
  912.             warning("can't create `%s'", new);
  913.             ncpfail++;
  914.             break;
  915.         case 'r':        /* read error */
  916.             warning("error reading `%s'", old);
  917.             ncpfail++;
  918.             break;
  919.         case 'w':        /* write error */
  920.             warning("error writing `%s'", new);
  921.             ncpfail++;
  922.             break;
  923.         default:
  924.             warning("error archiving `%s'", name);
  925.             ncpfail++;
  926.             break;
  927.         }
  928.  
  929.         /* limited patience for archiving failures */
  930.         if (ret != 0 && ncpfail == 3) {
  931.             warning("multiple archiving failures, forcing -h", "");
  932.             holdarch = 1;
  933.         }
  934.  
  935.         free(new);
  936.         free(old);
  937.         if (ret != 0)
  938.             return(-1);    /* without removing original */
  939.     }
  940.     if (unlink(artfile(name)) < 0) {
  941.         if (errno != ENOENT) {
  942.             warning("can't remove `%s'", name);
  943.             return(-1);
  944.         } else
  945.             nmissing++;
  946.     } else if (dir == NULL)
  947.         njunked++;
  948.     return(1);
  949. }
  950.  
  951. /*
  952.  - cp - try to copy an article (top level, administration)
  953.  */
  954. int                /* 0 success, other character failure */
  955. cp(old, new)
  956. char *old;            /* pathnames good from here */
  957. char *new;
  958. {
  959.     register int ret;
  960.     register int in, out;
  961.  
  962.     in = open(old, 0);
  963.     if (in < 0)
  964.         return('o');
  965.     out = creat(new, 0666);
  966.     if (out < 0) {
  967.         (void) close(in);
  968.         return('n');
  969.     }
  970.  
  971.     ret = cploop(in, out);
  972.  
  973.     (void) close(in);
  974.     if (fsync(out) < 0)
  975.         ret = 'w';
  976.     if (close(out) < 0)
  977.         ret = 'w';
  978.  
  979.     return(ret);
  980. }
  981.  
  982. /*
  983.  - cploop - try to copy an article (bottom level, copy loop)
  984.  */
  985. int                /* 0 success, other character failure */
  986. cploop(in, out)
  987. int in;
  988. int out;
  989. {
  990.     register int ret;
  991.     register int count;
  992.     register int firstblock = 1;
  993.  
  994.     while ((count = read(in, abuf, sizeof(abuf))) > 0) {
  995.         ret = write(out, abuf, count);
  996.         if (ret != count)
  997.             return('w');
  998.         if (firstblock) {
  999.             getsubj(abuf, count);
  1000.             firstblock = 0;
  1001.         }
  1002.     }
  1003.     if (count < 0)
  1004.         return('r');
  1005.  
  1006.     return(0);
  1007. }
  1008.  
  1009. /*
  1010.  - getsubj - try to find the Subject: line in a buffer
  1011.  *
  1012.  * Result goes in "subject", and is never empty.  Tabs become spaces,
  1013.  * since they are the output delimiters.
  1014.  */
  1015. void
  1016. getsubj(buf, bsize)
  1017. char *buf;
  1018. int bsize;
  1019. {
  1020.     register char *scan;
  1021.     register char *limit;
  1022.     register int len;
  1023.     register int clipped;
  1024.     static char sline[] = "Subject:";
  1025.  
  1026.     len = strlen(sline);
  1027.     limit = buf + bsize - len;
  1028.     for (scan = buf; scan < limit; scan++)
  1029.         if (CISTREQN(scan, sline, len) &&
  1030.                 (scan == buf || *(scan-1) == '\n')) {
  1031.             scan += len;
  1032.             for (limit = scan; limit < buf+bsize; limit++)
  1033.                 if (*limit == '\n')
  1034.                     break;
  1035.             while (scan < limit && isascii(*scan) && isspace(*scan))
  1036.                 scan++;
  1037.             len = limit-scan;
  1038.             clipped = 0;
  1039.             if (len > sizeof(subject)-1) {
  1040.                 len = sizeof(subject) - 1 - strlen("...");
  1041.                 clipped = 1;
  1042.             }
  1043.             if (len > 0) {
  1044.                 (void) strncpy(subject, scan, len);
  1045.                 subject[len] = '\0';
  1046.             } else
  1047.                 (void) strcpy(subject, "???");
  1048.             if (clipped)
  1049.                 (void) strcat(subject, "...");
  1050.             for (scan = strchr(subject, '\t'); scan != NULL;
  1051.                     scan = strchr(scan+1, '\t'))
  1052.                 *scan = ' ';
  1053.             return;
  1054.         } else if (*scan == '\n' && scan+1 < limit && *(scan+1) == '\n')
  1055.             break;        /* empty line terminates header */
  1056.  
  1057.     /* didn't find one -- fill in *something* */
  1058.     (void) strcpy(subject, "???");
  1059. }
  1060.  
  1061. /*
  1062.  - mkparents - try to make directories for archiving an article
  1063.  *
  1064.  * Assumes it can mess with first argument if it puts it all back at the end.
  1065.  */
  1066. void
  1067. mkparents(art, dir)
  1068. char *art;            /* name relative to dir */
  1069. char *dir;
  1070. {
  1071.     register char *cmd;
  1072.     register char *ocmd;
  1073.     register char *p;
  1074.  
  1075.     ocmd = str3save("PATH=", ctlfile("bin"), ":");
  1076.     cmd = str3save(ocmd, binfile((char *)NULL), ":");
  1077.     free(ocmd);
  1078.     ocmd = cmd;
  1079.     /* the semicolon here avoids problems with some buggy shells */
  1080.     cmd = str3save(ocmd, newspath(), " ; mkpdir ");
  1081.     free(ocmd);
  1082.     ocmd = cmd;
  1083.     cmd = str3save(ocmd, dir, "/");
  1084.     free(ocmd);
  1085.     p = strrchr(art, '/');
  1086.     *p = '\0';
  1087.     ocmd = cmd;
  1088.     cmd = str3save(ocmd, art, "");
  1089.     free(ocmd);
  1090.     *p = '/';
  1091.     (void) system(cmd);
  1092.     free(cmd);
  1093. }
  1094.  
  1095. char *months[12] = {
  1096.     "Jan",
  1097.     "Feb",
  1098.     "Mar",
  1099.     "Apr",
  1100.     "May",
  1101.     "Jun",
  1102.     "Jul",
  1103.     "Aug",
  1104.     "Sep",
  1105.     "Oct",
  1106.     "Nov",
  1107.     "Dec",
  1108. };
  1109.  
  1110. /*
  1111.  - printstuff - print information about an expiring article
  1112.  */
  1113. void
  1114. printstuff(msgid, name, recdate)
  1115. char *msgid;
  1116. char *name;
  1117. time_t recdate;
  1118. {
  1119.     struct tm *gmt;
  1120.  
  1121.     gmt = gmtime(&recdate);
  1122.     printf("%s\t%s\t%d-%s-%d\t%s\n", name, msgid, gmt->tm_mday,
  1123.             months[gmt->tm_mon], gmt->tm_year+1900, subject);
  1124. }
  1125.  
  1126. /*
  1127.  - eufopen - fopen, with fail if doesn't succeed
  1128.  */
  1129. FILE *
  1130. eufopen(name, mode)
  1131. char *name;
  1132. char *mode;
  1133. {
  1134.     FILE *f;
  1135.     static char grump[50] = "can't open `%s' for `";
  1136.  
  1137.     f = fopen(name, mode);
  1138.     if (f == NULL) {
  1139.         (void) strcat(grump, mode);
  1140.         (void) strcat(grump, "'");
  1141.         fail(grump, name);
  1142.     }
  1143.     return(f);
  1144. }
  1145.  
  1146. /*
  1147.  - eufclose - fclose with failure checking
  1148.  */
  1149. void
  1150. eufclose(f, name)
  1151. FILE *f;
  1152. char *name;
  1153. {
  1154.     if (nfclose(f) == EOF)
  1155.         fail("error in closing file `%s'", name);
  1156. }
  1157.  
  1158. /*
  1159.  - checkadir - check archiving directory is real, writable, and full pathname
  1160.  */
  1161. void                /* set -h if not */
  1162. checkadir(dir)
  1163. char *dir;
  1164. {
  1165.     struct stat stbuf;
  1166.     register int hforce = 0;
  1167. #    define    GRUMP(a,b)    {warning(a, b); hforce = 1;}
  1168.  
  1169.     if (*dir == '=')    /* disregard leading '=' */
  1170.         dir++;
  1171.     errno = 0;
  1172.     if (stat(dir, &stbuf) < 0)
  1173.         GRUMP("archiving directory `%s' does not exist", dir);
  1174.     if (access(dir, 02) < 0)
  1175.         GRUMP("archiving directory `%s' not writable", dir);
  1176.     if (dir[0] != '/')
  1177.         GRUMP("archiving directory `%s' not a full pathname", dir);
  1178.     if (hforce) {
  1179.         warning("forcing -h option as a stopgap", "");
  1180.         holdarch = 1;
  1181.     }
  1182. }
  1183.  
  1184. /*
  1185.  - back - get a date n days back, with overflow check
  1186.  *
  1187.  * Requires that "now" be set first.
  1188.  */
  1189. time_t
  1190. back(ndaystr)
  1191. char *ndaystr;
  1192. {
  1193.     register double goback;        /* how far before now it is */
  1194.  
  1195.     if (STREQ(ndaystr, "never"))
  1196.         goback = FOREVER;    /* > now-EPOCH */
  1197.     else
  1198.         goback = atof(ndaystr) * DAY;
  1199.  
  1200.     if (goback > now-EPOCH)        /* before EPOCH */
  1201.         return(EPOCH);
  1202.     return((time_t)(now - goback));
  1203. }
  1204.  
  1205. /*
  1206.  - printlists - print control lists for debugging
  1207.  */
  1208. void
  1209. printlists()
  1210. {
  1211.     register int i;
  1212.     register struct ctl *ct;
  1213.  
  1214.     fprintf(stderr, "control file:\n");
  1215.     for (ct = ctls; ct != NULL; ct = ct->next)
  1216.         pctl(ct);
  1217.     fprintf(stderr, "\n");
  1218.  
  1219.     for (i = 0; i < NHASH; i++)
  1220.         if (ngs[i] != NULL) {
  1221.             fprintf(stderr, "list %d:\n", i);
  1222.             for (ct = ngs[i]; ct != NULL; ct = ct->next)
  1223.                 pctl(ct);
  1224.         }
  1225.     fprintf(stderr, "\n");
  1226. }
  1227.  
  1228. /*
  1229.  - pctl - print one control-list entry
  1230.  */
  1231. void
  1232. pctl(ct)
  1233. register struct ctl *ct;
  1234. {
  1235. #    define    DAYS(x)    ((now-(x))/DAY)
  1236.  
  1237.     fprintf(stderr, "%s(%c) %.2f-%.2f-%.2f %s\n", ct->groups, ct->ismod,
  1238.             DAYS(ct->retain), DAYS(ct->normal), DAYS(ct->purge),
  1239.             (ct->dir == NULL) ? "(null)" : ct->dir);
  1240. }
  1241.  
  1242. /*
  1243.  - unprivileged - no-op needed to keep the pathname stuff happy
  1244.  */
  1245. void
  1246. unprivileged(reason)
  1247. char *reason;
  1248. {
  1249. }
  1250.  
  1251. /*
  1252.  - fail - call errunlock, possibly after cleanup
  1253.  */
  1254. void
  1255. fail(s1, s2)
  1256. char *s1;
  1257. char *s2;
  1258. {
  1259.     int saveerr = errno;
  1260.  
  1261.     if (spacetight) {
  1262.         cd(histdir);
  1263.         (void) remove("history.n");
  1264.         (void) remove("history.n.dir");
  1265.         (void) remove("history.n.pag");
  1266.     }
  1267.     errno = saveerr;
  1268.     errunlock(s1, s2);
  1269.     /* NOTREACHED */
  1270. }
  1271.  
  1272. /*
  1273.  - die - like fail, but errno contains no information
  1274.  */
  1275. void
  1276. die(s1, s2)
  1277. char *s1;
  1278. char *s2;
  1279. {
  1280.     errno = 0;
  1281.     fail(s1, s2);
  1282. }
  1283.  
  1284. /*
  1285.  - readline - read history line (sans newline), with locking when we hit EOF
  1286.  *
  1287.  * Data pointed to may be altered but not extended.  Note that initialization
  1288.  * is cleverly set up so that the first time this is called, it falls through
  1289.  * to the "hard case" logic.
  1290.  *
  1291.  * Minor flaw:  will lose a last line which lacks a newline.
  1292.  */
  1293. char *                /* NULL is EOF */
  1294. readline(fd)
  1295. int fd;                /* Note descriptor, not FILE *. */
  1296. {
  1297.     register char *line;        /* line buffer */
  1298.     register size_t linesize;
  1299.     register char *linep;        /* unused part of line buffer */
  1300.     register char *endp;        /* newline */
  1301.     register size_t len;        /* length of line (fragment) */
  1302.     register int n;
  1303.     extern void refill();
  1304.  
  1305.     /* try for the easy case -- whole line in buffer */
  1306.     endp = strchr(rest, '\n');
  1307.     if (endp != NULL) {
  1308.         *endp++ = '\0';
  1309.         rlnleft -= endp - rest;
  1310.         line = rest;
  1311.         rest = endp;
  1312.         return(line);
  1313.     }
  1314.  
  1315.     /* oh well, have to put it together in malloced area... */
  1316.     line = rlline;
  1317.     linesize = rllsiz;
  1318.     if (line == NULL) {
  1319.         line = malloc(linesize);
  1320.         if (line == NULL)
  1321.             fail("out of space when reading history", "");
  1322.     }
  1323.  
  1324.     linep = line;
  1325.     for (;;) {
  1326.         if (rlnleft <= 0) {
  1327.             refill(fd);
  1328.             if (rlnleft <= 0)    /* refill gave up. */
  1329.                 return(NULL);
  1330.         }
  1331.  
  1332.         endp = strchr(rest, '\n');
  1333.         if (endp == NULL)    /* hit the sentinel */
  1334.             len = rlnleft;
  1335.         else
  1336.             len = endp - rest + 1;
  1337.         while (linep + len > line + linesize) {    /* not enough room */
  1338.             linesize = (linesize * 3) / 2;
  1339.             n = linep - line;
  1340.             line = realloc(line, linesize);
  1341.             if (line == NULL)
  1342.                 fail("out of memory in readline", "");
  1343.             linep = line + n;
  1344.         }
  1345.  
  1346.         (void) memcpy(linep, rest, len);
  1347.         linep += len;
  1348.         rest += len;
  1349.         rlnleft -= len;
  1350.         if (endp != NULL) {
  1351.             *(linep-1) = '\0';
  1352.             rlline = line;
  1353.             rllsiz = linesize;
  1354.             return(line);
  1355.         }
  1356.     }
  1357.     /* NOTREACHED */
  1358. }
  1359.  
  1360. /*
  1361.  - refill - refill readline's buffer, with locking on EOF
  1362.  */
  1363. void
  1364. refill(fd)
  1365. int fd;
  1366. {
  1367.     register int ret;
  1368.  
  1369.     /* Just in case... */
  1370.     if (rlnleft > 0)
  1371.         return;
  1372.  
  1373.     /* Try ordinary read. */
  1374.     ret = read(fd, rlbuf, (int)rlbufsiz);
  1375.     if (ret < 0)
  1376.         fail("read error in history", "");
  1377.     if (ret > 0) {
  1378.         rlnleft = ret;
  1379.         rest = rlbuf;
  1380.         rlbuf[ret] = '\0';    /* sentinel */
  1381.         return;
  1382.     }
  1383.  
  1384.     /* EOF. */
  1385.     if (nlocked)
  1386.         return;        /* We're really done. */
  1387.  
  1388.     /* EOF but we haven't locked yet.  Lock and try again. */
  1389.     (void) signal(SIGINT, SIG_IGN);
  1390.     (void) signal(SIGQUIT, SIG_IGN);
  1391.     (void) signal(SIGHUP, SIG_IGN);
  1392.     (void) signal(SIGTERM, SIG_IGN);
  1393.     newslock();
  1394.     nlocked = 1;
  1395.     refill(fd);
  1396. }
  1397.