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