Attachment 'dbackfeed.c'
Download 1 /*
2 * dbackfeed.c
3 *
4 * 2007, Keith Bare <kbare@club.cc.cmu.edu>
5 *
6 * This program aids in backfeeding articles from one reader to another,
7 * while preserving article numbering.
8 *
9 * Many parts of this code were taken from parts of the Diablo
10 * distribution, notably dexpireover.c.
11 */
12
13 /*
14 * UTIL/DEXPIREOVER.C
15 *
16 * (c)Copyright 1998, Matthew Dillon, All Rights Reserved. Refer to
17 * the COPYRIGHT file in the base directory of this distribution
18 * for specific rights granted.
19 *
20 * LOCKING INFO
21 *
22 * The data file will have an advisory lock at offset 4 from dreaderd
23 * when it has the file open (can't rewrite data)
24 * The info file will have an advisory lock at offset 4 from dreaderd
25 * when it has the file open (can't resize)
26 *
27 * od_HFd = data. file
28 * ov_OFd = over. file
29 *
30 */
31
32
33 #include <assert.h>
34 #include <dreaderd/defs.h>
35
36
37 /*
38 * Types and constants
39 */
40
41 typedef struct Group {
42 struct Group *gr_Next;
43 int gr_State;
44 int gr_StartNo;
45 int gr_EndNo;
46 int gr_CTS;
47 int gr_LMTS;
48 int gr_Iter;
49 char *gr_GroupName;
50 char *gr_Flags;
51 char *gr_Hash;
52 int gr_UpdateFlag;
53 } Group;
54
55 #define GRF_DESCRIPTION 0x00000001
56 #define GRF_STARTNO 0x00000002
57 #define GRF_ENDNO 0x00000004
58 #define GRF_FLAGS 0x00000008
59 #define GRF_FROMLOCAL 0x00000800
60 #define GRF_NEW 0x00001000
61 #define GRF_FROMREMOTE 0x00002000
62 #define GRF_MODIFIED 0x00008000
63 #define GRF_EDITEDBEG 0x00010000
64
65 #define GHSIZE 1024
66 #define GHMASK (GHSIZE-1)
67
68 #define IO_BUFSIZE 2048
69
70
71 /*
72 * Globals
73 */
74
75 KPDB *KDBActive;
76 Group *GHash[GHSIZE];
77
78 int VerboseOpt = -1;
79 int ForReal = 1;
80 int MustExit = 0;
81 char *Wild;
82 char *OutgoingPath = "/tmp/dbackfeed-outgoing";
83 char *QueuePath = "/tmp/dbackfeed-queue";
84 size_t OutgoingTargetSize = 2UL * (2UL << 20); /* 2 MB */
85 char *HostName = NULL;
86 int HostPort = 119;
87
88 FILE *outgoingStream = NULL;
89 FILE *queueStream = NULL;
90 size_t outgoingBytes;
91
92 char DnewslinkCommand[1024];
93
94
95 /*
96 * Prototypes
97 */
98
99 void Usage(char *progname);
100 void ScanDirectories(void);
101 void scanDirectory(const char *dirpath, char *dirname, int *level);
102 void ProcessOverviewFile(const char *dirPath, const char *name);
103 void ProcessArticle(OverArt *oa, Group *group, const char *dirPath, int iter);
104 void ReadArticle(const char *msgid, char *headBase, int headLen, char *artPath, History *h);
105 void WriteArticleAndQueueFileEntry(const char *msgid, SpoolArtHdr *spoolArtHdr, char *headBase, int headLen, FILE *artStream);
106 void RunDnewslink(void);
107 char *allocTmpCopy(const char *buf, int bufLen);
108 int SetField(char **pptr, const char *str);
109 Group *EnterGroup(const char *groupName, int begNo, int endNo, int lmts, int cts, int iter, const char *flags);
110 Group *FindGroupByHash(char *Hash, int iter);
111 void ExtractMessageId(const char *scan, int scanLen, const char **pmsgid);
112
113
114 /*
115 * Function definitions
116 */
117
118 int
119 main(int ac, char **av)
120 {
121 int i;
122 char *dbfile = NULL;
123
124 LoadDiabloConfig(ac, av);
125
126 for (i = 1; i < ac; ++i) {
127 char *ptr = av[i];
128 if (*ptr != '-') {
129 fprintf(stderr, "Unexpected argument: %s\n", ptr);
130 Usage(av[0]);
131 }
132 ptr += 2;
133 switch(ptr[-1]) {
134 case 'f':
135 dbfile = (*ptr) ? ptr : av[++i];
136 break;
137 case 'h':
138 HostName = (*ptr) ? ptr : av[++i];
139 break;
140 case 'n':
141 ForReal = 0;
142 if (VerboseOpt < 0)
143 VerboseOpt = 1;
144 break;
145 case 'o':
146 OutgoingPath = (*ptr) ? ptr : av[++i];
147 break;
148 case 'p':
149 if (*ptr) {
150 errno = 0;
151 HostPort = (int)strtol(ptr, NULL, 0);
152 if (errno != 0) {
153 fprintf(stderr, "invalid number format for -p option\n");
154 Usage(av[0]);
155 exit(1);
156 }
157 } else {
158 fprintf(stderr, "the -p option requires an argument\n");
159 Usage(av[0]);
160 exit(1);
161 }
162 break;
163 case 'q':
164 QueuePath = (*ptr) ? ptr : av[++i];
165 break;
166 case 't':
167 if (*ptr) {
168 errno = 0;
169 OutgoingTargetSize = (size_t)strtoul(ptr, NULL, 0);
170 if (errno != 0) {
171 fprintf(stderr, "invalid number format for -t option\n");
172 Usage(av[0]);
173 exit(1);
174 }
175 } else {
176 fprintf(stderr, "the -t option requires an argument\n");
177 Usage(av[0]);
178 exit(1);
179 }
180 break;
181 case 'v':
182 VerboseOpt = (*ptr) ? strtol(ptr, NULL, 0) : 1;
183 break;
184 case 'w':
185 Wild = (*ptr) ? ptr : av[++i];
186 break;
187 /* Common options */
188 case 'C': /* parsed by LoadDiabloConfig */
189 if (*ptr == 0)
190 ++i;
191 break;
192 case 'd':
193 DebugOpt = (*ptr) ? strtol(ptr, NULL, 0) : 1;
194 break;
195 case 'V':
196 PrintVersion();
197 break;
198 default:
199 fprintf(stderr, "unknown option: %s\n", ptr - 2);
200 Usage(av[0]);
201 }
202 }
203
204 if (!HostName) {
205 fprintf(stderr, "the -h option is required\n");
206 Usage(av[0]);
207 exit(1);
208 }
209
210 /*
211 * Construct the dnewlink invocation
212 */
213 if (snprintf(DnewslinkCommand, sizeof(DnewslinkCommand),
214 "dnewslink -b %s -h %s -P %d",
215 QueuePath, HostName, HostPort) >=
216 sizeof(DnewslinkCommand))
217 {
218 printf("dnewslink command is too long\n");
219 exit(1);
220 }
221
222 /*
223 * Open active file database
224 */
225
226 if (VerboseOpt)
227 printf("Loading active file\n");
228 if (dbfile) {
229 KDBActive = KPDBOpen(dbfile, O_RDONLY);
230 } else {
231 KDBActive = KPDBOpen(PatDbExpand(ReaderDActivePat), O_RDONLY);
232 }
233 if (KDBActive == NULL) {
234 fprintf(stderr, "Unable to open dactive.kp\n");
235 exit(1);
236 }
237
238 /*
239 * scan dactive.kp
240 */
241
242 if (VerboseOpt)
243 printf("Hashing newsgroups\n");
244 {
245 int recLen;
246 int recOff;
247 int cts0 = (int)time(NULL);
248
249 for (recOff = KPDBScanFirst(KDBActive, 0, &recLen);
250 recOff;
251 recOff = KPDBScanNext(KDBActive, recOff, 0, &recLen)
252 ) {
253 int groupLen;
254 int flagsLen;
255 const char *rec = KPDBReadRecordAt(KDBActive, recOff, 0, NULL);
256 const char *group = KPDBGetField(rec, recLen, NULL, &groupLen, NULL);
257 const char *flags = KPDBGetField(rec, recLen, "S", &flagsLen, "y");
258 int begNo = strtol(KPDBGetField(rec, recLen, "NB", NULL, "-1"), NULL, 10);
259 int endNo = strtol(KPDBGetField(rec, recLen, "NE", NULL, "-1"), NULL, 10);
260 int lmts = (int)strtoul(KPDBGetField(rec, recLen, "LMTS", NULL, "0"), NULL, 16);
261 int cts = (int)strtoul(KPDBGetField(rec, recLen, "CTS", NULL, "0"), NULL, 16);
262 int iter = (int)strtoul(KPDBGetField(rec, recLen, "ITER", NULL, "0"), NULL, 16);
263 Group *grp;
264
265 if (cts == 0) /* enter non-zero cts only if group has no CTS field */
266 cts = cts0;
267 else
268 cts = 0;
269
270 if (group)
271 group = allocTmpCopy(group, groupLen);
272 if (flags)
273 flags = allocTmpCopy(flags, flagsLen);
274
275 /*
276 * ignore bad group or group that does not match the wildcard
277 */
278
279 if (group == NULL)
280 continue;
281 if (Wild && WildCmp(Wild, group) != 0)
282 continue;
283
284 grp = EnterGroup(
285 group,
286 begNo,
287 endNo,
288 lmts,
289 cts,
290 iter,
291 flags
292 );
293 grp->gr_State &= ~(GRF_NEW|GRF_MODIFIED);
294 }
295 }
296
297 /*
298 * Open the history
299 */
300
301 if (HistoryOpen(NULL, HGF_READONLY) < 0) {
302 printf("failed to open history\n");
303 return(-1);
304 }
305
306 /*
307 * Process the spool control file
308 */
309
310 LoadSpoolCtl(0, 1);
311
312 /*
313 * Do work
314 */
315
316 ScanDirectories();
317
318 if (outgoingStream != NULL) {
319 fclose(outgoingStream);
320 fclose(queueStream);
321
322 RunDnewslink();
323 }
324
325 /*
326 * Close the active file
327 */
328
329 if (KDBActive)
330 KPDBClose(KDBActive);
331
332 return(0);
333 }
334
335 void
336 Usage(char *progname)
337 {
338 fprintf(stderr, "Backfeed articles in the reader header database\n");
339 fprintf(stderr, "dbackfeed [-f active] -h host [-n] [-o outgoingfile] [-p#] [-q queuefile] [-t#] [-v#] [-w wildmat] [-C diablo.config] [-d[n]] [-V]\n");
340 fprintf(stderr, "\t-f file\t\tSpecify the name of the active file\n");
341 fprintf(stderr, "\t-h host\t\tName of host to backfeed to\n");
342 fprintf(stderr, "\t-n\t\tDon't actually make any changes (dry run)\n");
343 fprintf(stderr, "\t-o file\t\tSpecify location to store outgoing articles\n");
344 fprintf(stderr, "\t-p#\t\tPort number to backfeed to\n");
345 fprintf(stderr, "\t-q file\t\tSpecify location to store outgoing queue file\n");
346 fprintf(stderr, "\t-t#\t\tAccumulate at least this many bytes before sending\n");
347 fprintf(stderr, "\t-v[#]\t\tVerbose mode\n");
348 fprintf(stderr, "\t-w wildmat\tSpecify a wildmat of groups to expire\n");
349 fprintf(stderr, "\t-C file\tspecify diablo.config to use\n");
350 fprintf(stderr, "\t-d[n]\tset debug [with optional level]\n");
351 fprintf(stderr, "\t-V\tprint version and exit\n");
352 exit(1);
353 }
354
355 /*
356 * Scan /news/spool/group/ and
357 *
358 * This works by scanning all files in the directories, mapping the
359 * filename (hash) to a newsgroup name and performing the following,
360 * depending on file type:
361 *
362 * over.* :
363 * *.o.* :
364 *
365 * data.*:
366 * *.d.*:
367 *
368 * If a newsgroup for a file cannot be found, the file is ignored
369 */
370
371 void
372 ScanDirectories(void)
373 {
374 DIR *dir;
375 int level = 0;
376
377 if (VerboseOpt)
378 printf("Scanning group directories\n");
379
380 chdir(PatExpand(GroupHomePat));
381 if ((dir = opendir(".")) != NULL) {
382 den_t *den;
383 struct stat st;
384
385 while ((den = readdir(dir)) != NULL) {
386 if (isalnum((int)den->d_name[0]) &&
387 stat(den->d_name, &st) == 0 && S_ISDIR(st.st_mode)
388 /*
389 * We explicitly use the first char, because overview
390 * sizes appear to be not evenly distributed wrt second
391 * char.
392 */
393 )
394 scanDirectory(PatExpand(GroupHomePat), den->d_name, &level);
395 }
396 closedir(dir);
397 }
398 }
399
400 void
401 scanDirectory(const char *dirpath, char *dirname, int *level)
402 {
403 DIR *dir2;
404 char path[PATH_MAX];
405 char origpath[PATH_MAX];
406
407 sprintf(path, "%s/%s", dirpath, dirname);
408
409 if (getcwd(origpath, sizeof(origpath)) == NULL) {
410 printf("Unable to getcwd(%s): %s\n", dirname, strerror(errno));
411 return;
412 }
413 if (chdir(dirname) != 0) {
414 printf("Unable to chdir(%s)\n", dirname);
415 return;
416 }
417
418 if ((dir2 = opendir(".")) != NULL) {
419 den_t *den2;
420 struct stat st;
421
422 while ((den2 = readdir(dir2)) != NULL) {
423 if (strcmp(den2->d_name, ".") == 0 || strcmp(den2->d_name, "..") == 0)
424 continue;
425 if (stat(den2->d_name, &st) == 0 && S_ISDIR(st.st_mode)) {
426 if (*level <= 2)
427 scanDirectory(path, den2->d_name, level);
428 } else if (strncmp(den2->d_name, "over.", 5) == 0 ||
429 strncmp(den2->d_name, "o.", 2) == 0 ||
430 strstr(den2->d_name, ".o.") != NULL)
431 ProcessOverviewFile(path, den2->d_name);
432 if (MustExit)
433 exit(1);
434 }
435 }
436 if (chdir(origpath) != 0) {
437 printf("FATAL: Unable to return with chdir(%s)\n", dirname);
438 exit(1);
439 }
440
441 closedir(dir2);
442 }
443
444 /*
445 * ProcessOverviewFile() - process over. and data. files. All over. files
446 * are processed first.
447 *
448 * When processing over. files, we may resize the index array (-s)
449 * and/or cleanup the file (-R).
450 *
451 * When processing data. files, we typically remove whole files.
452 * If the -R option was used, however, we rewrite the files. We can
453 * safely copy/rename-over data. files as long as we are able to
454 * lock the associated over. file.
455 */
456
457 void
458 ProcessOverviewFile(const char *dirPath, const char *name)
459 {
460 long artBase = -1;
461 Group *group;
462 char path[PATH_MAX];
463 char Hash[PATH_MAX];
464 int iter = 0;
465 int oaFd = -1;
466 OverHead oh;
467 struct stat st;
468 int r;
469 OverArt *oaBase = NULL;
470 size_t oaSize;
471 int i;
472
473 snprintf(path, sizeof(path), "%s/%s", dirPath, name);
474
475 if (DebugOpt > 0)
476 printf("ProcessOverviewFile(%s)\n", path);
477
478 bzero(Hash, sizeof(Hash));
479
480 if (ExtractGroupHashInfo(name, Hash, &artBase, &iter) == HASHGRP_NONE)
481 return;
482
483 if (DebugOpt > 2)
484 printf("File: %s artBase=%ld iter=%d Hash=%s\n",
485 name, artBase, iter, Hash);
486
487 if ((group = FindGroupByHash(Hash, iter)) == NULL) {
488 if (!Wild) {
489 printf("Group for over file not found (%s)\n", path);
490 }
491 return;
492 }
493
494 if (DebugOpt > 2)
495 printf("Maps to group: %s\n", group->gr_GroupName);
496
497 /*
498 * over. file (fixed length file)
499 */
500
501 again:
502
503 oaFd = open(path, O_RDONLY);
504 if (oaFd < 0) {
505 return;
506 }
507
508 /*
509 * Leave a shared lock on the over.* file so expireover knows when
510 * it is OK to resize the file. If the file was renamed-over,
511 * we have to re-open it.
512 */
513
514 hflock(oaFd, 4, XLOCK_SH);
515
516 if (fstat(oaFd, &st) < 0 || st.st_nlink == 0) {
517 hflock(oaFd, 4, XLOCK_UN);
518 close(oaFd);
519 oaFd = -1;
520 goto again;
521 }
522
523 r = (read(oaFd, &oh, sizeof(oh)) == sizeof(oh));
524 if (oh.oh_Version > OH_VERSION ||
525 oh.oh_ByteOrder != OH_BYTEORDER)
526 r = 0;
527 if (r && oh.oh_Version > 1 &&
528 strcmp(oh.oh_Gname, group->gr_GroupName) != 0)
529 r = 0;
530 if (r) {
531 int n;
532
533 oaSize = st.st_size - oh.oh_HeadSize;
534 n = oaSize / sizeof(OverArt);
535
536 if (lseek(oaFd, 0, SEEK_SET) == (off_t)-1) {
537 printf("error rewinding overview file \"%s\" fd = %d (%s)\n",
538 path, oaFd, strerror(errno));
539 goto out;
540 }
541
542 oaBase = xmap(NULL, oaSize, PROT_READ, MAP_SHARED, oaFd, oh.oh_HeadSize);
543 if (oaBase == NULL) {
544 printf("error on overview mmap for group %s, file \"%s\" fd = %d (%s)\n",
545 group->gr_GroupName, path, oaFd, strerror(errno));
546 goto out;
547 }
548
549 for (i = 0; i < n; i++) {
550 if (oaBase[i].oa_ArtNo > 0) {
551 ProcessArticle(&oaBase[i], group, dirPath, iter);
552 }
553 }
554
555 } else {
556 printf("group %s, file \"%s\" bad file header\n",
557 group->gr_GroupName,
558 path
559 );
560 if (oh.oh_Version > OH_VERSION)
561 printf(" expected version %d, got version %d\n",
562 OH_VERSION, oh.oh_Version);
563 }
564
565 out:
566
567 if (oaBase) {
568 xunmap(oaBase, oaSize);
569 }
570 if (oaFd > 0) {
571 hflock(oaFd, 4, XLOCK_UN);
572 close(oaFd);
573 }
574
575 }
576
577 void
578 ProcessArticle(OverArt *oa, Group *group, const char *dirPath, int iter)
579 {
580 int artBase;
581 const char *dfName;
582 char dfPath[PATH_MAX];
583 int ohFd = -1;
584 struct stat st;
585 char *ohBase = NULL;
586 const char *msgid;
587 History h;
588 char artPath[PATH_MAX];
589
590 artBase = oa->oa_ArtNo & ~OD_HMASK;
591
592 /* Construct the path to the data file */
593
594 dfName = GFName(group->gr_GroupName, GRPFTYPE_DATA, artBase, 0, iter,
595 &DOpts.ReaderGroupHashMethod);
596
597 if (snprintf(dfPath, sizeof(dfPath), "%s/%s", dirPath, dfName) >=
598 sizeof(dfPath))
599 {
600 printf("error getting data file name, name too long\n");
601 goto out;
602 }
603
604 ohFd = open(dfPath, O_RDONLY);
605 if (ohFd < 0) {
606 printf("error opening data file for group %s, file \"%s\" (%s)\n",
607 group->gr_GroupName, dfPath, strerror(errno));
608 goto out;
609 }
610
611 if (fstat(ohFd, &st) < 0) {
612 goto out;
613 }
614
615 if (lseek(ohFd, 0, SEEK_SET) == (off_t)-1) {
616 printf("error rewinding overview file \"%s\" fd = %d (%s)\n",
617 dfPath, ohFd, strerror(errno));
618 goto out;
619 }
620
621 ohBase = xmap(NULL, st.st_size, PROT_READ, MAP_SHARED, ohFd, 0);
622 if (!ohBase) {
623 printf("error on data mmap for group %s, file \"%s\" fd = %d (%s)\n",
624 group->gr_GroupName, dfPath, ohFd, strerror(errno));
625 goto out;
626 }
627
628 ExtractMessageId(ohBase + oa->oa_SeekPos, oa->oa_Bytes, &msgid);
629
630 if (!strncmp(msgid, "<>", 2)) {
631 printf("failed to extract message id, group \"%s\", artNum %d\n",
632 group->gr_GroupName, oa->oa_ArtNo);
633 goto out;
634 }
635
636 if (HistoryLookup(msgid, &h) < 0) {
637 printf("message \"%s\" is in the overview, but not in "
638 "the history\n", msgid);
639 goto out;
640 }
641
642 ArticleFileName(artPath, sizeof(artPath), &h, ARTFILE_FILE);
643 if (!strncmp(artPath, "/dev/null", 9)) {
644 printf("message \"%s\" is in the history, but not in "
645 "the spool\n", msgid);
646 goto out;
647 }
648
649 if (ForReal) {
650 ReadArticle(msgid, ohBase + oa->oa_SeekPos, oa->oa_Bytes, artPath, &h);
651 }
652
653 out:
654
655 if (ohBase) {
656 xunmap(ohBase, st.st_size);
657 }
658 if (ohFd > 0) {
659 close(ohFd);
660 }
661 }
662
663 void
664 ReadArticle(const char *msgid, char *headBase, int headLen,
665 char *artPath, History *h)
666 {
667 FILE *artStream;
668 SpoolArtHdr spoolArtHdr;
669
670 if (VerboseOpt) {
671 printf("processing \"%s\" offset %d\n", artPath, h->boffset);
672 }
673
674 artStream = fopen(artPath, "rb");
675 if (!artStream) {
676 printf("couldn't open article file \"%s\"\n", artPath);
677 goto out;
678 }
679
680 if (fseek(artStream, h->boffset, SEEK_SET) < 0) {
681 printf("couldn't seek article file \"%s\" to %d\n",
682 artPath, h->boffset);
683 goto out;
684 }
685
686 if (fread(&spoolArtHdr, sizeof(SpoolArtHdr), 1, artStream) < 1) {
687 printf("io error reading article header from \"%s\" offset %d\n",
688 artPath, h->boffset);
689 goto out;
690 }
691
692 if (spoolArtHdr.Magic1 != STORE_MAGIC1 ||
693 spoolArtHdr.Magic2 != STORE_MAGIC2)
694 {
695 printf("bad magic in \"%s\" offset %d\n", artPath, h->boffset);
696 goto out;
697 }
698
699 if (spoolArtHdr.Version != STOREAPI_REVISION) {
700 printf("wrong version in \"%s\" offset %d\n", artPath, h->boffset);
701 goto out;
702 }
703
704 if (fseek(artStream, spoolArtHdr.ArtHdrLen, SEEK_CUR) < 0)
705 {
706 printf("couldn't seek article file\"%s\" past SpoolArtHdr "
707 "at offset %d\n", artPath, h->boffset);
708 goto out;
709 }
710
711 WriteArticleAndQueueFileEntry(msgid, &spoolArtHdr, headBase,
712 headLen, artStream);
713
714 out:
715
716 if (artStream) {
717 fclose(artStream);
718 }
719 }
720
721 void
722 WriteArticleAndQueueFileEntry(const char *msgid, SpoolArtHdr *spoolArtHdr,
723 char *headBase, int headLen, FILE *artStream)
724 {
725 long artOffset;
726 SpoolArtHdr outgoingArtHdr;
727 size_t bytesWritten;
728 char buf[IO_BUFSIZE];
729 size_t bytesToRead;
730 size_t bytesRead;
731
732 char *headbuf = NULL;
733 size_t headbufLen = 0;
734 int i;
735
736 headbuf = alloca(headLen);
737
738 for (i = 0; i < headLen - 1; i++) {
739 if (headBase[i] == '\r' && headBase[i + 1] == '\n') {
740 headbuf[headbufLen++] = '\n';
741 i++;
742 } else {
743 headbuf[headbufLen++] = headBase[i];
744 }
745 }
746
747 if (i < headLen) {
748 headbuf[headbufLen++] = headBase[i];
749 }
750
751 if (!outgoingStream) {
752 assert(queueStream == NULL);
753
754 outgoingStream = fopen(OutgoingPath, "wb");
755 if (!outgoingStream) {
756 printf("couldn't open output spool file \"%s\"\n",
757 OutgoingPath);
758 exit(1);
759 }
760 queueStream = fopen(QueuePath, "w");
761 if (!queueStream) {
762 printf("couldn't open output queue file \"%s\"\n",
763 QueuePath);
764 exit(1);
765 }
766 outgoingBytes = 0;
767 }
768
769 artOffset = ftell(outgoingStream);
770 if (artOffset == -1) {
771 printf("couldn't find current position in outgoingStream\n");
772 return;
773 }
774
775 memcpy(&outgoingArtHdr, spoolArtHdr, sizeof(SpoolArtHdr));
776 outgoingArtHdr.HeadLen = sizeof(SpoolArtHdr);
777 outgoingArtHdr.ArtHdrLen = headbufLen;
778 outgoingArtHdr.ArtLen += headbufLen - spoolArtHdr->ArtHdrLen;
779 outgoingArtHdr.StoreLen = sizeof(SpoolArtHdr) + outgoingArtHdr.ArtLen + 1;
780
781 bytesWritten = fwrite(&outgoingArtHdr, 1, sizeof(SpoolArtHdr),
782 outgoingStream);
783 if (bytesWritten < sizeof(SpoolArtHdr)) {
784 printf("io error writing SpoolArtHdr to output spool file \"%s\"\n",
785 OutgoingPath);
786 exit(1);
787 }
788 outgoingBytes += bytesWritten;
789
790 {
791
792 bytesWritten = fwrite(headbuf, 1, headbufLen, outgoingStream);
793 if (bytesWritten < headbufLen) {
794 printf("io error writing headers to output spool file \"%s\"\n",
795 OutgoingPath);
796 exit(1);
797 }
798 outgoingBytes += bytesWritten;
799 }
800
801 bytesToRead = spoolArtHdr->ArtLen - spoolArtHdr->ArtHdrLen;
802 while (bytesToRead > 0) {
803 bytesRead = fread(buf, 1, bytesToRead > sizeof(buf) ?
804 sizeof(buf) : bytesToRead, artStream);
805 if (bytesRead == 0) {
806 printf("io error reading input spool file\n");
807 exit(1);
808 }
809 bytesWritten = fwrite(buf, 1, bytesRead, outgoingStream);
810 if (bytesWritten < bytesRead) {
811 printf("io error writing article to output spool file \"%s\"\n",
812 OutgoingPath);
813 exit(1);
814 }
815 outgoingBytes += bytesWritten;
816 bytesToRead -= bytesRead;
817 }
818
819 if (fputc('\0', outgoingStream) == EOF) {
820 printf("io error writing nul to output spool file \"%s\"\n",
821 OutgoingPath);
822 exit(1);
823 }
824 outgoingBytes += 1;
825
826 if (fprintf(queueStream, "%s %s %ld,%u\n", OutgoingPath, msgid,
827 artOffset, outgoingArtHdr.StoreLen - 1) == 0)
828 {
829 printf("io error writing entry to queue file \"%s\"\n",
830 QueuePath);
831 exit(1);
832 }
833
834 if (outgoingBytes > OutgoingTargetSize) {
835 /* temporarily just close files, and wait for user to hit enter. */
836 fclose(outgoingStream);
837 outgoingStream = NULL;
838 fclose(queueStream);
839 queueStream = NULL;
840
841 RunDnewslink();
842 }
843 }
844
845 void
846 RunDnewslink(void)
847 {
848 int val;
849
850 if (VerboseOpt) {
851 printf("running dnewslink\n");
852 }
853 val = system(DnewslinkCommand);
854 if (!WIFEXITED(val) || WEXITSTATUS(val) != 0) {
855 printf("dnewslink failed\n");
856 exit(1);
857 }
858 }
859
860
861 int
862 SetField(char **pptr, const char *str)
863 {
864 if (*pptr && strcmp(*pptr, str) == 0)
865 return(0);
866 if (*pptr)
867 free(*pptr);
868 *pptr = strcpy(malloc(strlen(str) + 1), str);
869 return(1);
870 }
871
872 char *
873 allocTmpCopy(const char *buf, int bufLen)
874 {
875 static char *SaveAry[8];
876 static int SaveCnt;
877 char **pptr;
878
879 SaveCnt = (SaveCnt + 1) % arysize(SaveAry);
880 pptr = &SaveAry[SaveCnt];
881 if (*pptr)
882 free(*pptr);
883 *pptr = malloc(bufLen + 1);
884 memcpy(*pptr, buf, bufLen);
885 (*pptr)[bufLen] = 0;
886 return(*pptr);
887 }
888
889 Group *
890 EnterGroup(const char *groupName, int begNo, int endNo, int lmts, int cts, int iter, const char *flags)
891 {
892 hash_t hv = hhash(GFHash(groupName, &DOpts.ReaderGroupHashMethod));
893 Group **pgroup = &GHash[hv.h1 & GHMASK];
894 Group *group;
895
896 if (DebugOpt > 0)
897 printf("EnterGroup(%s)\n", groupName);
898
899 if (DebugOpt > 1)
900 printf("Group: %s Hash: %08x.%08x\n", groupName, hv.h1, hv.h2);
901
902 while ((group = *pgroup) != NULL) {
903 if (strcmp(groupName, group->gr_GroupName) == 0)
904 break;
905 pgroup = &group->gr_Next;
906 }
907 if (group == NULL) {
908 *pgroup = group = calloc(sizeof(Group) + strlen(groupName) + 1, 1);
909 group->gr_State = GRF_NEW;
910 group->gr_GroupName = (char *)(group + 1);
911 group->gr_Hash = strdup(GFHash(groupName, &DOpts.ReaderGroupHashMethod));
912 group->gr_Iter = iter;
913 strcpy(group->gr_GroupName, groupName);
914 }
915
916 /*
917 * update fields
918 */
919 if (begNo >= 0) {
920 group->gr_State |= GRF_STARTNO;
921 if (group->gr_StartNo != begNo) {
922 group->gr_State |= GRF_MODIFIED;
923 group->gr_StartNo = begNo;
924 }
925 }
926 if (endNo >= 0) {
927 group->gr_State |= GRF_ENDNO;
928 if (endNo < group->gr_EndNo) {
929 printf("*** Would be adjusting NE down - not doing it ***\n");
930 printf("adjust %s NE from %d to %d\n", group->gr_GroupName,
931 endNo, group->gr_EndNo);
932 } else if (group->gr_EndNo != endNo) {
933 group->gr_EndNo = endNo;
934 group->gr_State |= GRF_MODIFIED;
935 }
936 }
937 group->gr_LMTS = lmts;
938
939 if (cts) {
940 group->gr_CTS = cts;
941 }
942
943 if (flags) {
944 group->gr_State |= GRF_FLAGS;
945 if (SetField(&group->gr_Flags, flags))
946 group->gr_State |= GRF_MODIFIED;
947 }
948 return(group);
949 }
950
951 Group *
952 FindGroupByHash(char *Hash, int iter)
953 {
954 Group *group;
955 hash_t hv = hhash(Hash);
956
957 for (group = GHash[hv.h1 & GHMASK]; group; group = group->gr_Next) {
958 if (strcmp(group->gr_Hash, Hash) == 0 && group->gr_Iter == iter)
959 break;
960 }
961 return(group);
962 }
963
964 /*
965 * ExtractMessageId()
966 *
967 * This code was taken from dreaderd/group.c.
968 */
969
970 void
971 ExtractMessageId(const char *scan, int scanLen, const char **pmsgid)
972 {
973 while (scanLen > 0) {
974 int i;
975 char ch = tolower(scan[0]);
976 for (i = 0; i < scanLen && scan[i] != '\n'; ++i)
977 ;
978 if (pmsgid != NULL && ch == 'm' &&
979 strncasecmp(scan, "Message-ID:", 11) == 0) {
980 int b = 11;
981 int e;
982 char buf[MAXMSGIDLEN];
983
984 while (b < scanLen && (scan[b] == ' ' || scan[b] == '\t'))
985 ++b;
986 e = b;
987 while (e < scanLen && (scan[e] != '>'))
988 ++e;
989 if (e < scanLen)
990 ++e;
991 if (e - b < MAXMSGIDLEN) {
992 bcopy(scan + b, buf, e - b);
993 buf[e-b] = 0;
994 *pmsgid = MsgId(buf, NULL);
995 }
996 /*
997 * Stop scanning through headers
998 */
999 break;
1000 }
1001
1002 if (i < scanLen)
1003 ++i;
1004 scanLen -= i;
1005 scan += i;
1006 }
1007 }
Attached Files
To refer to attachments on a page, use attachment:filename, as shown below in the list of files. Do NOT use the URL of the [get] link, since this is subject to change and can break easily.You are not allowed to attach a file to this page.