home *** CD-ROM | disk | FTP | other *** search
/ Unix System Administration Handbook 1997 October / usah_oct97.iso / news / cnews.tar / relay / fileart.c < prev    next >
C/C++ Source or Header  |  1995-04-27  |  23KB  |  811 lines

  1. /*
  2.  * fileart - file an article, given its temporary file name and its headers
  3.  *
  4.  * It may be desirable to, some day, prevent cross-postings across
  5.  * "universes", where a universe might be "alt" or "comp,news".
  6.  *
  7.  * There are three classes of newsgroup for the purposes of filing:
  8.  * "wanted" (in the active file and missing the "x" flag);
  9.  * "not wanted" ("x"ed in active, or not in active and not matched by sys
  10.  *    file's subscription list for this machine), so ignore it; or
  11.  * "don't know it" (not in active and matched by subscription list,
  12.  *    so file the article in junk once, iff there are no good groups).
  13.  * junk *must* be in the active file or it's an error (ST_DROPPED),
  14.  * but junk may have an "x" flag to prevent filing.
  15.  *
  16.  * Use the active file 'x' flag to snuff groups quietly, even when your
  17.  * subscription list permits them, without filing in junk.
  18.  *
  19.  * Constraints.
  20.  *
  21.  * Article filing is more subtle than it looks at first, and there are quite
  22.  * a few constraints on it.  The problems are primarily with cross-posted
  23.  * articles.  We prefer to make real Unix links where possible, so that
  24.  * each newsgroup mentioned costs only a directory entry and each article
  25.  * an i-node.  Where this isn't feasible (the news spool is split across
  26.  * file systems or the operating system doesn't support links), we try to
  27.  * make symbolic links.  Where that isn't feasible (the underlying
  28.  * operating system doesn't support symbolic links), we make copies; this
  29.  * is costly in disk space and bandwidth, but provides a worst-case
  30.  * fall-back strategy.
  31.  *
  32.  * To complicate the situation, we may have to create a temporary link for
  33.  * an article with a message header too large to fit in memory, so that we
  34.  * can dump what we have in memory to disk and thence copy the rest of the
  35.  * article there.  And if we must generate an Xref: header (either because
  36.  * the article is cross-posted or because we were asked on the command line
  37.  * to do so), we clearly must emit it before we emit any of the message body,
  38.  * so the names of the links must all be known for certain upon reaching
  39.  * the end of the message header, which means we must at least have created
  40.  * all the links by then, even if they aren't all populated.  In fact, on
  41.  * systems where we must make copies, we will have to go back and make a
  42.  * second pass to populate one link per filesystem.
  43.  *
  44.  * It is tempting to mandate that message headers shall be small, but they
  45.  * continue to grow over time and gatewayed RFC 822 mail messages may contain
  46.  * arbitrary numbers of large Received: headers, for example.
  47.  */
  48.  
  49. #include <stdio.h>
  50. #include <stdlib.h>
  51. #include <ctype.h>
  52. #include <string.h>
  53. #include <errno.h>
  54. #include "fixerrno.h"
  55. #include <sys/types.h>
  56. #include <sys/stat.h>
  57.  
  58. #include "libc.h"
  59. #include "news.h"
  60. #include "config.h"
  61. #include "headers.h"
  62. #include "relay.h"
  63. #include "active.h"
  64. #include "history.h"
  65. #include "ngmatch.h"
  66. #include "system.h"
  67.  
  68. #define XREFDELIM ':'
  69.  
  70. /*
  71.  * If a_unlink is true, there is a temporary link.
  72.  *
  73.  * If tmplink is false, use a_tmpf to hold the name of the first permanent link,
  74.  * and art->a_artf as its stdio stream.  !tmplink means "Newsgroups:" was seen
  75.  * in time, and is normally true.
  76.  * If tmplink is true, just make links to a_tmpf, which is already open as
  77.  * art->a_artf.  This is only the case if we had to dump the headers early.
  78.  */
  79. #define tmplink(art)    (art)->a_unlink
  80.  
  81. /* imports */
  82. extern void prefuse();
  83.  
  84. /* privates */
  85. struct link {
  86.     char    *l_grp;            /* group name, not directory */
  87.     char     *l_num;            /* NULL or article number */
  88.     char    l_type;            /* 's', 'l' or '\0' */
  89.     boolean    l_fillme;        /* still needs work? */
  90.     dev_t    l_dev;            /* -1 if not known */
  91. };
  92. static struct link *links;        /* array of links */
  93. static struct link *link1;        /* first permanent link */
  94. static struct link *linklim;        /* just past last link in use */
  95. static struct link templink;
  96. static struct link *tlp;
  97. static long artnum;            /* asgnartnum sets artnum */
  98. static int goodngs;            /* asgnartnum reads goodngs */
  99. static int junkgroups;            /* count "junked" groups */
  100. static boolean debug = NO;
  101. static boolean slinknoted = NO;        /* have we noted use of symlinks? */
  102. extern char *slinkfile;            /* in relaynews.c */
  103.  
  104. static char dbglink[] = "linking `%s' to `%s'... ";
  105. static char dbgsymlink[] = "symlinking `%s' to `%s'... ";
  106. static char dbgempty[] = "couldn't link or symlink; making empty `%s'... ";
  107. static char dbgopen[] = "opening `%s'... ";
  108. static char logxcljunk[] = "no known groups in `%s' and %s group is excluded in active\n";
  109. static char lognojunk[] = "no known groups in `%s' and no %s group\n";
  110. static char logxcl[] = "all groups `%s' excluded in active\n";
  111. static char dbgcopy[] = "couldn't link or symlink; copying `%s' to `%s'... ";
  112.  
  113. /* forward */
  114. STATIC int lnkcmp();
  115.  
  116. void
  117. filedebug(state)        /* set debugging state */
  118. boolean state;
  119. {
  120.     debug = state;
  121. }
  122.  
  123. /* Append ng/artnumstr to art's list of files, and bump goodngs. */
  124. STATIC void
  125. gotgoodng(art, lp)
  126. struct article *art;
  127. struct link *lp;
  128. {
  129.     ++goodngs;
  130.     histupdfiles(art, lp->l_grp, lp->l_num);
  131. }
  132.  
  133. STATIC
  134. filllink(lp, grp, num, type, fillme, dev)
  135. register struct link *lp;
  136. char *grp, *num;
  137. char type;
  138. boolean fillme;
  139. dev_t dev;
  140. {
  141.     lp->l_grp = grp;
  142.     lp->l_num = num;
  143.     lp->l_type = type;
  144.     lp->l_fillme = fillme;
  145.     lp->l_dev = dev;
  146. }
  147.  
  148. STATIC char *                    /* malloced */
  149. linkname(lp)
  150. register struct link *lp;
  151. {
  152.     register char *name;
  153.  
  154.     if (lp->l_grp == NULL || lp->l_grp[0] == '\0')
  155.         name = strsave(lp->l_num);    /* mostly for SPOOLTMP */
  156.     else {
  157.         name = str3save(lp->l_grp, SFNDELIM, lp->l_num);
  158.         mkfilenm(name);
  159.     }
  160.     return name;
  161. }
  162.  
  163. STATIC int
  164. trylink(olp, artname, lp)
  165. char *artname;
  166. register struct link *olp, *lp;
  167. {
  168.     register int worked = NO;
  169.  
  170.     if (newsconf.nc_link) {            /* e.g. Unix */
  171.         char *oname = linkname(olp);
  172.  
  173.         if (debug)
  174.             (void) fprintf(stderr, dbglink, oname, artname);
  175.         worked = link(oname, artname) == 0;
  176.         if (worked && lp != NULL) {
  177.             lp->l_type = 'l';
  178.             lp->l_dev = olp->l_dev;
  179.         }
  180.         free(oname);
  181.     }
  182.     return worked;
  183. }
  184.  
  185. STATIC int
  186. trysymlink(olp, artname, lp)
  187. char *artname;
  188. register struct link *olp, *lp;
  189. {
  190.     register int worked = NO;
  191.  
  192.     if (newsconf.nc_symlink) {            /* e.g. 4.2, Eunice */
  193.         char *oname = linkname(olp);
  194.  
  195.         if (debug)
  196.             (void) fprintf(stderr, dbgsymlink, fullartfile(oname),
  197.                 artname);
  198.         worked = symlink(fullartfile(oname), artname) == 0;
  199.         if (worked && lp != NULL)
  200.             lp->l_type = 's';
  201.         if (worked && !slinknoted && slinkfile != NULL) {
  202.             register FILE *f = fopen(slinkfile, "w");
  203.  
  204.             if (f != NULL) {
  205.                 fprintf(f, "%ld\n", (long)time((time_t *)NULL));
  206.                 fclose(f);
  207.             }
  208.             slinknoted = 1;
  209.         }
  210.         free(oname);
  211.     }
  212.     return worked;
  213. }
  214.  
  215. /*
  216.  * we have to create the links before we have seen the entire article,
  217.  * so just make empty links for now; later on, we will copy into them.
  218.  */
  219. STATIC int
  220. trycopy(artname, lp)
  221. char *artname;
  222. register struct link *lp;
  223. {
  224.     register FILE *out;
  225.     register int worked = NO;
  226.     struct stat statb;
  227.  
  228.     if (debug)
  229.         (void) fprintf(stderr, dbgempty, artname);
  230.     out = fopenexcl(artname);
  231.     worked = out != NULL;
  232.     if (worked) {
  233.         lp->l_fillme = YES;        /* revisit me later */
  234.         lp->l_type = 'l';
  235.         if (fstat(fileno(out), &statb) >= 0)
  236.             lp->l_dev = statb.st_dev;
  237.         (void) fclose(out);
  238.     }
  239.     return worked;
  240. }
  241.  
  242. /*
  243.  * If we get here, things look pretty bleak.
  244.  * Links to the first link have failed.
  245.  * We either have no link facilities at all (e.g. Plan 9),
  246.  * or we're trying to link across file systems and can't (e.g. V7).
  247.  * Making copies wastes space, so try to make a link
  248.  * to any other link, if possible, first.
  249.  * If all else fails, make a copy; with luck a later link can be made to it.
  250.  */
  251. STATIC int
  252. tryanything(olp, artname, lp)
  253. char *artname;
  254. register struct link *olp, *lp;
  255. {
  256.     register int worked = NO;
  257.     register struct link *plp;
  258.     char *destdir = strsave(artname);
  259.     char *slp = strrchr(destdir, FNDELIM);
  260.     struct stat statb;
  261.  
  262.     /* have we got a link on this device already? */
  263.     if (slp != NULL)
  264.         *slp = '\0';        /* form directory name */
  265.     if (stat(destdir, &statb) >= 0)
  266.         lp->l_dev = statb.st_dev;
  267.     free(destdir);
  268.     for (plp = link1; plp < lp; plp++)
  269.         if (plp->l_dev == lp->l_dev && plp->l_dev != (dev_t)-1)
  270.             break;
  271.     if (plp < lp)            /* yes, we do */
  272.         if (plp->l_type == 'l') {
  273.             worked = trylink(plp, artname, lp);
  274.             if (!worked)
  275.                 worked = trysymlink(link1, artname, lp);
  276.             /* else make a copy */
  277.         } else if (plp->l_type == 's' && olp->l_type != 's')
  278.             worked = trysymlink(link1, artname, lp);
  279.     if (!worked)
  280.         worked = trycopy(artname, lp);
  281.     return worked;
  282. }
  283.  
  284. filetmp(art)                /* make temporary link */
  285. register struct article *art;
  286. {
  287.     if (art->a_artf == NULL) {
  288.         nnfree(&art->a_tmpf);
  289.         art->a_tmpf = strsave(SPOOLTMP);
  290.         (void) mktemp(art->a_tmpf);
  291.         art->a_unlink = YES;
  292.         art->a_artf = fopenwclex(art->a_tmpf, "w+");
  293.         if (art->a_artf == NULL)
  294.             persistent(art, '\0', "", "");    /* can't open article */
  295.     }
  296. }
  297.  
  298. STATIC boolean
  299. fileopen(art, lp, artname)        /* create first permanent link */
  300. register struct article *art;
  301. register struct link *lp;
  302. char *artname;
  303. {
  304.     register boolean worked = NO;
  305.     struct stat statb;
  306.  
  307.     if (debug)
  308.         (void) fprintf(stderr, dbgopen, artname);
  309.     nnfree(&art->a_tmpf);
  310.     art->a_tmpf = strsave(artname);
  311.     art->a_artf = fopenexcl(art->a_tmpf);
  312.     worked = art->a_artf != NULL;
  313.     if (worked && lp != NULL) {
  314.         lp->l_type = 'l';
  315.         if (fstat(fileno(art->a_artf), &statb) >= 0)
  316.             lp->l_dev = statb.st_dev;
  317.     }
  318.     return worked;
  319. }
  320.  
  321. /*
  322.  * Try to make a link of "olp" to artname.
  323.  * If "olp" is NULL, record and open artname iff no
  324.  * link yet exists to that name (by any file, to avoid overwriting
  325.  * existing articles, e.g. due to an out-of-date active file).
  326.  * Result goes in "lp".
  327.  */
  328. STATIC boolean
  329. openorlink(artname, art, olp, lp)
  330. register char *artname;
  331. register struct article *art;
  332. struct link *olp, *lp;
  333. {
  334.     register boolean worked = NO;    /* open or link worked? */
  335.  
  336.     errno = 0;            /* paranoia */
  337.     if (olp == NULL)
  338.         worked = fileopen(art, lp, artname);
  339.     else {                /* temp. or perm. link(s) exist */
  340.         /* try links to the first link first */
  341.         worked = trylink(olp, artname, lp);
  342.         if (!worked && errno != ENOENT && olp->l_type != 's')
  343.             worked = trysymlink(link1, artname, lp);
  344.         if (!worked && errno != ENOENT)    /* e.g. v7 over fs's, Plan 9 */
  345.             worked = tryanything(olp, artname, lp);
  346.     }
  347.     if (debug)
  348.         if (worked)
  349.             (void) fprintf(stderr, "success.\n");
  350.         else
  351.             warning("failed.", "");
  352.     return worked;
  353. }
  354.  
  355. /*
  356.  * Try to link art to artname.
  357.  * If the attempt fails, maybe some intermediate directories are missing,
  358.  * so create any missing directories and try again.  If the second attempt
  359.  * also fails, look at errno; if it is EEXIST, artname already exists
  360.  * (presumably because the active file is out of date, or the existing
  361.  * file is a directory such as net/micro/432), so indicate that higher
  362.  * levels should keep trying, otherwise we are unable to create the
  363.  * necessary directories, so complain and set bad status in art.
  364.  *
  365.  * Returns YES iff there is no point in trying to file this article again,
  366.  * usually because it has been successfully filed, but sometimes because
  367.  * the necessary directories cannot be made.
  368.  */
  369. STATIC boolean
  370. mkonelink(art, olp, lp, lnkstatp, artname)
  371. register struct article *art;
  372. struct link *olp, *lp;
  373. statust *lnkstatp;
  374. register char *artname;
  375. {
  376.     if (openorlink(artname, art, olp, lp))
  377.         return YES;
  378.     else if (errno == ENOENT) {
  379.         (void) mkdirs(artname, getuid(), getgid());
  380.         if (openorlink(artname, art, olp, lp))
  381.             return YES;
  382.         else if (errno != EEXIST) {
  383.             persistent(NOART, 'f', "can't link to `%s'", artname);
  384.             *lnkstatp |= ST_DROPPED; /* really can't make a link */
  385.             return YES;        /* hopeless - give up */
  386.         } else
  387.             return NO;
  388.     } else
  389.         return NO;
  390. }
  391.  
  392. /*
  393.  * Construct a link name (slashng/artnum) for this article,
  394.  * and try to link "olp" to it, with results in "lp".
  395.  *
  396.  * We changed directory to spooldir in main(), so the generated name
  397.  * is relative to spooldir, therefore artname can be used as is.
  398.  *
  399.  * Return value is the same as mkonelink's.
  400.  */
  401. STATIC boolean
  402. tryartnum(art, olp, lp, lnkstatp)
  403. struct article *art;
  404. register struct link *olp, *lp;
  405. statust *lnkstatp;
  406. {
  407.     register char *artname;        /* article file name */
  408.     register boolean ret;
  409.     char artnumstr[30];
  410.  
  411.     (void) sprintf(artnumstr, "%ld", artnum);
  412.     nnfree(&lp->l_num);        /* in case we are trying again */
  413.     lp->l_num = strsave(artnumstr);
  414.     artname = linkname(lp);
  415.     ret = mkonelink(art, olp, lp, lnkstatp, artname);
  416.     free(artname);
  417.     return ret;
  418. }
  419.  
  420. /*
  421.  * Assign a permanent name and article number to the existing link "olp",
  422.  * in newsgroup lp->l_grp & store the ascii form of the article
  423.  * number into lp->l_num, returning the article number in "artnum".
  424.  * If lp->l_num is non-null initially, it's the ascii article number
  425.  * to file under.
  426.  *
  427.  * If tmplink is false and goodngs is zero, set inname to artname,
  428.  * fopen artname and store the result in art->a_artf.
  429.  */
  430. STATIC statust
  431. asgnartnum(art, olp, lp)
  432. register struct article *art;
  433. register struct link *olp, *lp;
  434. {
  435.     statust lnkstat = ST_OKAY;
  436.  
  437.     /* field active 'x' flag: don't file this group, quietly */
  438.     if (unwanted(lp->l_grp)) {
  439.         artnum = -1;
  440.         logaudit(art, 'a', "group `%s' is 'x'ed", lp->l_grp);
  441.         return ST_REFUSED;
  442.     }
  443.     if (lp->l_num != NULL) {        /* number supplied? */
  444.         artnum = atol(lp->l_num);    /* believe number */
  445.         if (!tryartnum(art, olp, lp, &lnkstat)) {
  446.             char *s1;
  447.             char number[30];
  448.  
  449.             (void) sprintf(number, "%ld", artnum);
  450.             s1 = str3save("article #", number,
  451.                       " in group `%s' supplied but occupied!");
  452.             errno = 0;
  453.             transient(NOART, 'f', s1, lp->l_grp);
  454.             free(s1);
  455.             lnkstat |= ST_DROPPED;
  456.         }
  457.     } else
  458.         while ((artnum = nxtartnum(lp->l_grp)) >= 1 &&
  459.             !tryartnum(art, olp, lp, &lnkstat))
  460.                 ;
  461.     return lnkstat;
  462. }
  463.  
  464. /*
  465.  * File once in "junk" iff no ngs were filed due to absence from
  466.  * active, but some were permitted by sys.  This will make one junk
  467.  * link, no matter how many bad groups, and only if all are bad
  468.  * (e.g. rec.drugs,talk.chew-the-fat).
  469.  */
  470. STATIC void
  471. mkjunklink(art)
  472. register struct article *art;
  473. {
  474.     register statust lnkstat = ST_OKAY;
  475.     struct link jlink;
  476.     register struct link *jlp = &jlink;
  477.  
  478.     if (goodngs != 0)
  479.         return;        /* shouldn't be here, with valid groups */
  480.  
  481.     if (junkgroups > 0) {
  482.         /* All groups were "junked"; try to file this article in junk */
  483.         filllink(jlp, JUNK, (char *)NULL, '\0', NO, (dev_t)-1);
  484.         lnkstat = asgnartnum(art, tlp, jlp);
  485.         art->a_status |= lnkstat;
  486.         if (artnum >= 1 && lnkstat == ST_OKAY) {
  487.             gotgoodng(art, jlp);
  488.             logaudit(art, '\0', "article junked", "");
  489.             art->a_status |= ST_JUNKED;
  490.         } else
  491.         /* couldn't file article in junk.  why? */
  492.         if (lnkstat&ST_REFUSED) {    /* junk is 'x'ed */
  493.             prefuse(art);
  494.             (void) printf(logxcljunk, art->h.h_ngs, JUNK);
  495.         } else {            /* junk is missing? */
  496.             persistent(art, 'f',
  497.         "can't file in %s group; is it absent from active?", JUNK);
  498.             art->a_status |= ST_REFUSED;
  499.             prefuse(art);
  500.             (void) printf(lognojunk, art->h.h_ngs, JUNK);
  501.         }
  502.     } else {
  503.         /*
  504.          * Groups were permitted by subscription list, but all
  505.          * were 'x'ed in active, or otherwise refused.
  506.          */
  507.         if (opts.histreject)
  508.             history(art, NOLOG);
  509.         prefuse(art);
  510.         (void) printf(logxcl, art->h.h_ngs);
  511.         transient(art, '\0', "article rejected due to groups", "");
  512.     }
  513. }
  514.  
  515. static char *
  516. xrefngs(art)        /* hack up Xref: value and return ng:num pairs */
  517. register struct article *art;
  518. {
  519.     register char *ngs = NULL, *site, *groups;
  520.  
  521.     errno = 0;
  522.     if (art->h.h_xref == NULL)
  523.         transient(art, 'b', "no Xref: in believe-Xref mode", "");
  524.     else {
  525.         for (site = groups = skipsp(art->h.h_xref);
  526.              *groups != '\0' && isascii(*groups) && !isspace(*groups);
  527.              groups++)
  528.             ;            /* skip over site name */
  529.         if (*groups != '\0')
  530.             *groups++ = '\0';    /* terminate site name */
  531.         groups = skipsp(groups);
  532.         if (!STREQ(site, opts.blvsite))
  533.             transient(art, 'b',
  534.     "article received from wrong site `%s' in believe-Xref mode", site);
  535.         else
  536.             ngs = groups;        /* site is cool; rest is ngs */
  537.     }
  538.     return ngs;
  539. }
  540.  
  541. static char *
  542. histngs(art)
  543. register struct article *art;
  544. {
  545.     register char *ngs = NULL, *site, *groups = NULL, *histent = NULL;
  546.     static char *lasthist;
  547.  
  548.     if (lasthist != NULL)
  549.         free(lasthist);
  550.     lasthist = histent = gethistory(art->h.h_msgid);
  551.     errno = 0;
  552.     if (histent == NULL)
  553.         transient(art, 'b', "no history entry in duplicate-feed mode",
  554.             "");
  555.     else if ((groups = findfiles(histent)) == NULL)
  556.         transient(art, 'b',
  557.             "expired history entry in duplicate-feed mode", "");
  558.     else {
  559.         site = strsvto(art->h.h_path, '!');
  560.         if (!STREQ(site, opts.dupsite))
  561.             transient(art, 'b',
  562.     "article received from wrong site `%s' in duplicate-feed mode", site);
  563.         else {
  564.             ngs = groups;
  565.             /* convert group/art list to group:art list */
  566.             stranslit(ngs, FNDELIM, XREFDELIM);
  567.         }
  568.         free(site);
  569.     }
  570.     return ngs;
  571. }
  572.  
  573. /*
  574.  * extract list of newsgroups (and possibly article numbers) from article
  575.  * headers and history file, as options indicate.  If article numbers are
  576.  * being dictated by incoming Xref: or old history entry, the article numbers
  577.  * will be attached to the end of the group names by a colon as in Xref:
  578.  * (e.g. comp.lang.c:25780,general:12).
  579.  */
  580. static char *
  581. extngs(art)
  582. register struct article *art;
  583. {
  584.     register char *ngs = NULL, *groups;
  585.  
  586.     errno = 0;
  587.     if (opts.blvxref)
  588.         ngs = xrefngs(art);
  589.     else if (opts.dupsokay)
  590.         ngs = histngs(art);
  591.     else {
  592.         groups = art->h.h_ctlcmd != NULL? CONTROL: art->h.h_ngs; /* NCMP */
  593.         if (strchr(groups, XREFDELIM) != NULL)
  594.             transient(art, 'b',
  595.                 "colon not permitted in Newsgroups: list", ""); 
  596.         else
  597.             ngs = groups;
  598.     }
  599.     if (ngs == NULL)
  600.         transient(art, '\0', "no groups in headers", "");
  601.     else {
  602.         /* convert any whitespace to commas for fileart */
  603.         stranslit(ngs, ' ', NGSEP);
  604.         stranslit(ngs, '\t', NGSEP);        /* probably overkill */
  605.     }
  606.     return ngs;
  607. }
  608.  
  609. STATIC boolean
  610. linkonce(art, olp, lp)
  611. register struct article *art;
  612. register struct link *olp, *lp;
  613. {
  614.     register statust lst = asgnartnum(art, olp, lp);
  615.     register boolean ret;
  616.  
  617.     ret = artnum >= 1 && lst == ST_OKAY;
  618.     if (ret)
  619.         gotgoodng(art, lp);
  620.     else if (!(lst&ST_REFUSED) && ngpatmat(oursys()->sy_trngs, lp->l_grp))
  621.             ++junkgroups;
  622.     art->a_status |= lst&~ST_REFUSED;
  623.     return ret;
  624. }
  625.  
  626. STATIC struct link *                /* malloced */
  627. parsengs(ngs)                    /* parse ngs into links array */
  628. register char *ngs;
  629. {
  630.     register char *ng, *comma, *numb;
  631.     register struct link *lp;
  632.  
  633.     links = lp = (struct link *)
  634.         nemalloc((unsigned)(charcount(ngs, NGSEP)+1) * sizeof *links);
  635.     for (; ngs != NULL; ngs = comma) {
  636.         ngs = skipsp(ngs);        /* allow for user stupidity */
  637.         STRCHR(ngs, NGSEP, comma);
  638.         if (comma != NULL)
  639.             *comma = '\0';        /* will be restored below */
  640.         STRCHR(ngs, XREFDELIM, numb);    /* in case of opts.blvxref */
  641.         if (numb != NULL)        /* number supplied? */
  642.             *numb++ = '\0';        /* cleave number from group */
  643.  
  644.         ng = realngname(ngs);
  645.         if (ng == NULL)
  646.             ng = strsave(ngs);
  647.         if (ng[0] != '\0')        /* ignore null groups */
  648.             filllink(lp++, ng, (numb == NULL? NULL: strsave(numb)),
  649.                 '\0', NO, (dev_t)-1);
  650.         else
  651.             free(ng);
  652.         if (numb != NULL)        /* number supplied? */
  653.             *--numb = XREFDELIM;    /* restore lost byte */
  654.         if (comma != NULL)
  655.             *comma++ = NGSEP;    /* step past comma */
  656.     }
  657.     linklim = lp;
  658.     return links;
  659. }
  660.  
  661. /*
  662.  * Store in spooldir.  Link temp file to spooldir/ng/article-number
  663.  * for each ng.  Control messages go in CONTROL.
  664.  *
  665.  * The plan is: for each newsgroup, map the group name to its local
  666.  * equivalent (due to = active flag, etc.) for filing, try to file the
  667.  * article, if (no such group in active or link failed, and the group
  668.  * wasn't 'x'ed in active, but our subscription list permits this group),
  669.  * then set flag to file it under "junk" later, if the article number was
  670.  * assigned and the link succeeded, then update art->a_files list for history,
  671.  * and finally clear ST_REFUSED in the article's status, since only this
  672.  * single group was refused (by asgnartnum).
  673.  */
  674. STATIC void
  675. mklinks(art)
  676. register struct article *art;
  677. {
  678.     register struct link *lp, *olp;
  679.     register char *ngs;
  680.     struct stat statb;
  681.  
  682.     if (art->a_filed)
  683.         return;                /* don't file twice */
  684.     art->a_filed = YES;            /* make a note */
  685.     if (art->a_status&ST_REFUSED)
  686.         canthappen(art, 'i',
  687.             "mklinks called with ST_REFUSED set (can't happen)", "");
  688.     artnum = goodngs = junkgroups = 0;
  689.     ngs = extngs(art);
  690.     if (ngs == NULL) {
  691.         link1 = links = NULL;
  692.         return;
  693.     }
  694.     link1 = links = parsengs(ngs);
  695.  
  696.     /* sort links and strip duplicates (due to =grp or user stupidity) */
  697.     qsort((char *)links, (size_t)(linklim - links), sizeof *links, lnkcmp);
  698.     olp = links;
  699.     for (lp = links + 1; lp < linklim; lp++)
  700.         if (STREQ(olp->l_grp, lp->l_grp))
  701.             lp->l_grp[0] = '\0';    /* mark the link as a dup */
  702.         else
  703.             olp = lp;        /* starting a new group */
  704.  
  705.     /* make first permanent link without using symlinks. */
  706.     if (tmplink(art)) {  /* we've already got one; belatedly register it */
  707.         tlp = &templink;
  708.         /* 's' is a white lie: it prevents symlinks to this link */
  709.         filllink(tlp, "", art->a_tmpf, 's', NO, (art->a_artf != NULL &&
  710.             fstat(fileno(art->a_artf), &statb) >= 0?
  711.             statb.st_dev: (dev_t)-1));
  712.     } else
  713.         tlp = NULL;
  714.     for (lp = links; lp < linklim; lp++)
  715.         if (lp->l_grp[0] != '\0')    /* not a duplicate link? */
  716.              if (linkonce(art, tlp, lp)) {
  717.                 link1 = lp++;
  718.                 break;    /* kept trying until the sucker took */
  719.             }
  720.  
  721.     /* create all links after 1st that took; copies to be filled in later */
  722.     for (; lp < linklim; lp++)
  723.         if (lp->l_grp[0] != '\0')    /* not a duplicate link? */
  724.             (void) linkonce(art, link1, lp);
  725. }
  726.  
  727. STATIC int
  728. lnkcmp(a1, a2)
  729. char *a1, *a2;
  730. {
  731.     return strcmp(((struct link *)a1)->l_grp, ((struct link *)a2)->l_grp);
  732. }
  733.  
  734. /*
  735.  * File in the spool directory the article in art & fill in art->a_files.
  736.  * Generate Xref: header if needed (successfully cross-posted).
  737.  * (N.B.: Thus must be called before emitting any article body!)
  738.  */
  739. void
  740. fileart(art)
  741. register struct article *art;
  742. {
  743.     mklinks(art);
  744.     if (goodngs == 0)
  745.         mkjunklink(art);
  746.     /* -g or article crossposted, and article is open? */
  747.     if ((opts.genxref && goodngs > 0 || goodngs > 1) && art->a_artf != NULL)
  748.         emitxref(art);
  749. }
  750.  
  751. /*
  752.  * we have to create the links before we have seen the entire article,
  753.  * so just make empty links for now; later on, we will copy into them.
  754.  */
  755. STATIC int
  756. fill(oname, in, artname, lp)
  757. char *oname, *artname;
  758. FILE *in;
  759. register struct link *lp;
  760. {
  761.     register FILE *out;
  762.     register int worked = NO;
  763.     struct stat statb;
  764.  
  765.     if (debug)
  766.         (void) fprintf(stderr, dbgcopy, oname, artname);
  767.     out = fopen(artname, "w");
  768.     worked = out != NULL;
  769.     if (worked) {
  770.         register int cnt;
  771.         char buf[8192];
  772.  
  773.         rewind(in);
  774.         while ((cnt = fread(buf, 1, sizeof buf, in)) > 0)
  775.             (void) fwrite(buf, 1, cnt, out);
  776.         lp->l_type = 'l';
  777.         if (fstat(fileno(out), &statb) >= 0)
  778.             lp->l_dev = statb.st_dev;
  779.         lp->l_fillme = NO;
  780.         worked = fclose(out) != EOF;
  781.     }
  782.     return worked;
  783. }
  784.  
  785. mkcopies(art)            /* if copies must be made, fill them in here */
  786. register struct article *art;
  787. {
  788.     register struct link *lp;
  789.  
  790.     if (art->a_status&ST_REFUSED)
  791.         canthappen(art, 'i',
  792.             "mkcopies called with ST_REFUSED set (can't happen)", "");
  793.     if (links == NULL)    /* fileart failed? */
  794.         return;
  795.  
  796.     /* fill in any empty links */
  797.     for (lp = link1; lp < linklim; lp++) {
  798.         if (lp->l_fillme) {
  799.             char *artname = linkname(lp);
  800.  
  801.             if (!fill(art->a_tmpf, art->a_artf, artname, lp))
  802.                 persistent(art, '\0',
  803.                     "can't fill an empty link", "");
  804.             free(artname);
  805.         }
  806.         nnfree(&lp->l_grp);
  807.         nnfree(&lp->l_num);
  808.     }
  809.     free((char *)links);
  810. }
  811.