1 /* $Id: batcher.c 6762 2004-05-17 04:24:53Z rra $
3 ** Read batchfiles on standard input and spew out batches.
15 #include "inn/innconf.h"
16 #include "inn/messages.h"
17 #include "inn/timer.h"
26 static bool BATCHopen;
27 static bool STATprint;
28 static double STATbegin;
29 static double STATend;
31 static char *InitialString;
33 static char *Processor;
34 static int ArtsInBatch;
35 static int ArtsWritten;
36 static int BATCHcount;
37 static int MaxBatches;
38 static int BATCHstatus;
39 static long BytesInBatch = 60 * 1024;
40 static long BytesWritten;
43 static sig_atomic_t GotInterrupt;
44 static const char *Separator = "#! rnews %ld";
48 ** Start a batch process.
56 if (Processor && *Processor) {
57 snprintf(buff, sizeof(buff), Processor, Host);
71 ** Close a batch, return exit status.
78 return fflush(stdout) == EOF ? 1 : 0;
84 ** Update the batch file and exit.
87 RequeueAndExit(off_t Cookie, char *line, long BytesInArt)
89 static char LINE1[] = "batcher %s times user %.3f system %.3f elapsed %.3f";
90 static char LINE2[] ="batcher %s stats batches %d articles %d bytes %ld";
92 char buff[BIG_BUFFER];
99 STATend = TMRnow_double();
100 if (GetResourceUsage(&usertime, &systime) < 0) {
106 printf(LINE1, Host, usertime, systime, STATend - STATbegin);
108 printf(LINE2, Host, BATCHcount, ArtsWritten, BytesWritten);
112 syslog(L_NOTICE, LINE1, Host, usertime, systime, STATend - STATbegin);
113 syslog(L_NOTICE, LINE2, Host, BATCHcount, ArtsWritten, BytesWritten);
115 /* Last batch exit okay? */
116 if (BATCHstatus == 0) {
117 if (feof(stdin) && Cookie != -1) {
118 /* Yes, and we're all done -- remove input and exit. */
126 /* Make an appropriate spool file. */
128 spool = concatpath(innconf->pathoutgoing, Host);
130 spool = concat(Input, ".bch", (char *) 0);
131 if ((F = xfopena(spool)) == NULL)
132 sysdie("%s cannot open %s", Host, spool);
134 /* If we can back up to where the batch started, do so. */
136 if (Cookie != -1 && fseeko(stdin, Cookie, SEEK_SET) == -1) {
137 syswarn("%s cannot seek", Host);
141 /* Write the line we had; if the fseeko worked, this will be an
142 * extra line, but that's okay. */
143 if (line && fprintf(F, "%s %ld\n", line, BytesInArt) == EOF) {
144 syswarn("%s cannot write spool", Host);
148 /* Write rest of stdin to spool. */
149 while (fgets(buff, sizeof buff, stdin) != NULL)
150 if (fputs(buff, F) == EOF) {
151 syswarn("%s cannot write spool", Host);
155 if (fclose(F) == EOF) {
156 syswarn("%s cannot close spool", Host);
160 /* If we had a named input file, try to rename the spool. */
161 if (Input != NULL && rename(spool, Input) < 0) {
162 syswarn("%s cannot rename spool", Host);
172 ** Mark that we got interrupted.
175 CATCHinterrupt(int s)
179 /* Let two interrupts kill us. */
185 main(int ac, char *av[])
189 const char *AltSpool;
192 char line[BIG_BUFFER];
193 char buff[BIG_BUFFER];
206 openlog("batcher", L_OPENLOG_FLAGS | LOG_PID, LOG_INN_PROG);
207 message_program_name = "batcher";
208 if (!innconf_read(NULL))
213 ERRLOG = concatpath(innconf->pathlog, _PATH_ERRLOG);
216 while ((i = getopt(ac, av, "a:A:b:B:i:N:p:rs:S:v")) != EOF)
222 ArtsInBatch = atoi(optarg);
225 MaxArts = atol(optarg);
228 BytesInBatch = atol(optarg);
231 MaxBytes = atol(optarg);
234 InitialString = optarg;
237 MaxBatches = atoi(optarg);
255 if (MaxArts && ArtsInBatch == 0)
256 ArtsInBatch = MaxArts;
257 if (MaxBytes && BytesInBatch == 0)
258 BytesInBatch = MaxBytes;
260 /* Parse arguments. */
263 if (ac != 1 && ac != 2)
266 if ((Input = av[1]) != NULL) {
268 Input = concatpath(innconf->pathoutgoing, av[1]);
269 if (freopen(Input, "r", stdin) == NULL)
270 sysdie("%s cannot open %s", Host, Input);
274 freopen(ERRLOG, "a", stderr);
276 /* Go to where the articles are. */
277 if (chdir(innconf->patharticles) < 0)
278 sysdie("%s cannot chdir to %s", Host, innconf->patharticles);
280 /* Set initial counters, etc. */
282 data = xmalloc(datasize);
286 GotInterrupt = false;
287 xsignal(SIGHUP, CATCHinterrupt);
288 xsignal(SIGINT, CATCHinterrupt);
289 xsignal(SIGTERM, CATCHinterrupt);
290 /* xsignal(SIGPIPE, CATCHinterrupt); */
291 STATbegin = TMRnow_double();
295 while (fgets(line, sizeof line, stdin) != NULL) {
296 /* Record line length in case we do an ftello. Not portable to
297 * systems with non-Unix file formats. */
298 length = strlen(line);
299 Cookie = ftello(stdin) - length;
301 /* Get lines like "name size" */
302 if ((p = strchr(line, '\n')) == NULL) {
303 warn("%s skipping %.40s: too long", Host, line);
307 if (line[0] == '\0' || line[0] == '#')
309 if ((p = strchr(line, ' ')) != NULL) {
311 /* Try to be forgiving of bad input. */
312 BytesInArt = CTYPE(isdigit, (int)*p) ? atol(p) : -1;
317 /* Strip of leading spool pathname. */
319 && line[strlen(innconf->patharticles)] == '/'
320 && strncmp(line, innconf->patharticles, strlen(innconf->patharticles)) == 0)
321 p = line + strlen(innconf->patharticles) + 1;
327 token = TextToToken(p);
328 if ((art = SMretrieve(token, RETR_ALL)) == NULL) {
329 if ((SMerrno != SMERR_NOENT) && (SMerrno != SMERR_UNINIT))
330 warn("%s skipping %.40s: %s", Host, p, SMerrorstr);
334 artdata = FromWireFmt(art->data, art->len, (size_t *)&BytesInArt);
337 warn("%s skipping %.40s: not token", Host, p);
341 /* Have an open article, do we need to open a batch? This code
342 * is here (rather then up before the while loop) so that we
343 * can avoid sending an empty batch. The goto makes the code
344 * a bit more clear. */
347 RequeueAndExit(Cookie, (char *)NULL, 0L);
349 if ((F = BATCHstart()) == NULL) {
350 syswarn("%s cannot start batch %d", Host, BATCHcount);
353 if (InitialString && *InitialString) {
354 fprintf(F, "%s\n", InitialString);
355 BytesInCB += strlen(InitialString) + 1;
356 BytesWritten += strlen(InitialString) + 1;
361 /* We're writing a batch, see if adding the current article
362 * would exceed the limits. */
363 if ((ArtsInBatch > 0 && ArtsInCB + 1 >= ArtsInBatch)
364 || (BytesInBatch > 0 && BytesInCB + BytesInArt >= BytesInBatch)) {
365 if ((BATCHstatus = BATCHclose(F)) != 0) {
366 if (BATCHstatus == -1)
367 syswarn("%s cannot close batch %d", Host, BATCHcount);
369 syswarn("%s batch %d exit status %d", Host, BATCHcount,
376 /* See if we can start a new batch. */
377 if ((MaxBatches > 0 && BATCHcount >= MaxBatches)
378 || (MaxBytes > 0 && BytesWritten + BytesInArt >= MaxBytes)
379 || (MaxArts > 0 && ArtsWritten + 1 >= MaxArts)) {
384 RequeueAndExit(Cookie, (char *)NULL, 0L);
387 if ((F = BATCHstart()) == NULL) {
388 syswarn("%s cannot start batch %d", Host, BATCHcount);
394 /* Now we can start to send the article! */
395 if (Separator && *Separator) {
396 snprintf(buff, sizeof(buff), Separator, BytesInArt);
397 BytesInCB += strlen(buff) + 1;
398 BytesWritten += strlen(buff) + 1;
399 if (fprintf(F, "%s\n", buff) == EOF || ferror(F)) {
400 syswarn("%s cannot write separator", Host);
405 /* Write the article. In case of interrupts, retry the read but not
406 the fwrite because we can't check that reliably and portably. */
407 if ((fwrite(artdata, 1, BytesInArt, F) != BytesInArt) || ferror(F))
410 /* Update the counts. */
411 BytesInCB += BytesInArt;
412 BytesWritten += BytesInArt;
418 BATCHstatus = BATCHclose(F);
419 RequeueAndExit(Cookie, line, BytesInArt);
424 BATCHstatus = BATCHclose(F);
425 RequeueAndExit(Cookie, NULL, 0);