chiark / gitweb /
do not use closed ipf->fd to signal eof; implement [perhaps_][x]close
[inn-innduct.git] / backends / buffchan.c
1 /*  $Id: buffchan.c 6163 2003-01-19 22:56:34Z rra $
2 **
3 **  Buffered file exploder for innd.
4 */
5
6 #include "config.h"
7 #include "clibrary.h"
8 #include <ctype.h>
9 #include <errno.h>
10 #include <signal.h>
11 #include <sys/stat.h>
12
13 #include "inn/innconf.h"
14 #include "inn/messages.h"
15 #include "inn/qio.h"
16 #include "libinn.h"
17 #include "paths.h"
18 #include "map.h"
19
20 /*
21 **  Hash functions for hashing sitenames.
22 */
23 #define SITE_HASH(Name, p, j)    \
24         for (p = Name, j = 0; *p; ) j = (j << 5) + j + *p++
25 #define SITE_SIZE       128
26 #define SITE_BUCKET(j)  &SITEtable[j & (SITE_SIZE - 1)]
27
28
29 /*
30 **  Entry for a single active site.
31 */
32 typedef struct _SITE {
33     bool        Dropped;
34     const char  *Name;
35     int         CloseLines;
36     int         FlushLines;
37     time_t      LastFlushed;
38     time_t      LastClosed;
39     int         CloseSeconds;
40     int         FlushSeconds;
41     FILE        *F;
42     const char  *Filename;
43     char        *Buffer;
44 } SITE;
45
46
47 /*
48 **  Site hashtable bucket.
49 */
50 typedef struct _SITEHASH {
51     int         Size;
52     int         Used;
53     SITE        *Sites;
54 } SITEHASH;
55
56
57 /* Global variables. */
58 static char     *Format;
59 static const char *Map;
60 static int      BufferMode;
61 static int      CloseEvery;
62 static int      FlushEvery;
63 static int      CloseSeconds;
64 static int      FlushSeconds;
65 static sig_atomic_t     GotInterrupt;
66 static SITEHASH SITEtable[SITE_SIZE];
67 static TIMEINFO Now;
68
69
70 /*
71 **  Set up the site information.  Basically creating empty buckets.
72 */
73 static void
74 SITEsetup(void)
75 {
76     SITEHASH    *shp;
77
78     for (shp = SITEtable; shp < ARRAY_END(SITEtable); shp++) {
79         shp->Size = 3;
80         shp->Sites = xmalloc(shp->Size * sizeof(SITE));
81         shp->Used = 0;
82     }
83 }
84
85
86 /*
87 **  Close a site
88 */
89 static void
90 SITEclose(SITE *sp)
91 {
92     FILE        *F;
93
94     if ((F = sp->F) != NULL) {
95         if (fflush(F) == EOF || ferror(F)
96          || fchmod((int)fileno(F), 0664) < 0
97          || fclose(F) == EOF)
98             syswarn("%s cannot close %s", sp->Name, sp->Filename);
99         sp->F = NULL;
100     }
101 }
102
103 /*
104 **  Close all open sites.
105 */
106 static void
107 SITEcloseall(void)
108 {
109     SITEHASH    *shp;
110     SITE        *sp;
111     int i;
112
113     for (shp = SITEtable; shp < ARRAY_END(SITEtable); shp++)
114         for (sp = shp->Sites, i = shp->Used; --i >= 0; sp++)
115             SITEclose(sp);
116 }
117
118
119 /*
120 **  Open the file for a site.
121 */
122 static void SITEopen(SITE *sp)
123 {
124     int                 e;
125
126     if ((sp->F = xfopena(sp->Filename)) == NULL
127      && ((e = errno) != EACCES || chmod(sp->Filename, 0644) < 0
128       || (sp->F = xfopena(sp->Filename)) == NULL)) {
129         syswarn("%s cannot fopen %s", sp->Name, sp->Filename);
130         if ((sp->F = fopen("/dev/null", "w")) == NULL)
131             /* This really should not happen. */
132             sysdie("%s cannot fopen /dev/null", sp->Name);
133     }
134     else if (fchmod((int)fileno(sp->F), 0444) < 0)
135         syswarn("%s cannot fchmod %s", sp->Name, sp->Filename);
136         
137     if (BufferMode != '\0')
138         setbuf(sp->F, sp->Buffer);
139
140     /* Reset all counters. */
141     sp->FlushLines = 0;
142     sp->CloseLines = 0;
143     sp->LastFlushed = Now.time;
144     sp->LastClosed = Now.time;
145     sp->Dropped = false;
146 }
147
148
149 /*
150 **  Find a site, possibly create if not found.
151 */
152 static SITE *
153 SITEfind(char *Name, bool CanCreate)
154 {
155     char        *p;
156     int i;
157     unsigned int        j;
158     SITE        *sp;
159     SITEHASH            *shp;
160     char                c;
161     char                buff[BUFSIZ];
162
163     /* Look for site in the hash table. */
164     SITE_HASH(Name, p, j);
165     shp = SITE_BUCKET(j);
166     for (c = *Name, sp = shp->Sites, i = shp->Used; --i >= 0; sp++)
167         if (c == sp->Name[0] && strcasecmp(Name, sp->Name) == 0)
168             return sp;
169     if (!CanCreate)
170         return NULL;
171
172     /* Adding a new site -- grow hash bucket if we need to. */
173     if (shp->Used == shp->Size - 1) {
174         shp->Size *= 2;
175         shp->Sites = xrealloc(shp->Sites, shp->Size * sizeof(SITE));
176     }
177     sp = &shp->Sites[shp->Used++];
178
179     /* Fill in the structure for the new site. */
180     sp->Name = xstrdup(Name);
181     snprintf(buff, sizeof(buff), Format, Map ? MAPname(Name) : sp->Name);
182     sp->Filename = xstrdup(buff);
183     if (BufferMode == 'u')
184         sp->Buffer = NULL;
185     else if (BufferMode == 'b')
186         sp->Buffer = xmalloc(BUFSIZ);
187     SITEopen(sp);
188
189     return sp;
190 }
191
192
193 /*
194 **  Flush a site -- close and re-open the file.
195 */
196 static void
197 SITEflush(SITE *sp)
198 {
199     FILE        *F;
200
201     if ((F = sp->F) != NULL) {
202         if (fflush(F) == EOF || ferror(F)
203          || fchmod((int)fileno(F), 0664) < 0
204          || fclose(F) == EOF)
205             syswarn("%s cannot close %s", sp->Name, sp->Filename);
206         sp->F = NULL;
207     }
208     if (!sp->Dropped)
209         SITEopen(sp);
210 }
211
212
213 /*
214 **  Flush all open sites.
215 */
216 static void
217 SITEflushall(void)
218 {
219     SITEHASH    *shp;
220     SITE        *sp;
221     int i;
222
223     for (shp = SITEtable; shp < ARRAY_END(SITEtable); shp++)
224         for (sp = shp->Sites, i = shp->Used; --i >= 0; sp++)
225             SITEflush(sp);
226 }
227
228
229 /*
230 **  Write data to a site.
231 */
232 static void
233 SITEwrite(char *name, char *text, size_t len)
234 {
235     SITE        *sp;
236
237     sp = SITEfind(name, true);
238     if (sp->F == NULL)
239         SITEopen(sp);
240
241     if (fwrite(text, 1, len, sp->F) != len)
242         syswarn("%s cannot write", sp->Name);
243
244     /* Bump line count; see if time to close or flush. */
245     if (CloseEvery && ++(sp->CloseLines) >= CloseEvery) {
246         SITEflush(sp);
247         return;
248     }
249     if (CloseSeconds && sp->LastClosed + CloseSeconds < Now.time) {
250         SITEflush(sp);
251         return;
252     }
253     if (FlushEvery && ++(sp->FlushLines) >= FlushEvery) {
254         if (fflush(sp->F) == EOF || ferror(sp->F))
255             syswarn("%s cannot flush %s", sp->Name, sp->Filename);
256         sp->LastFlushed = Now.time;
257         sp->FlushLines = 0;
258     }
259     else if (FlushSeconds && sp->LastFlushed + FlushSeconds < Now.time) {
260         if (fflush(sp->F) == EOF || ferror(sp->F))
261             syswarn("%s cannot flush %s", sp->Name, sp->Filename);
262         sp->LastFlushed = Now.time;
263         sp->FlushLines = 0;
264     }
265 }
266
267
268 /*
269 **  Handle a command message.
270 */
271 static void
272 Process(char *p)
273 {
274     SITE        *sp;
275
276     if (*p == 'b' && strncmp(p, "begin", 5) == 0)
277         /* No-op. */
278         return;
279
280     if (*p == 'f' && strncmp(p, "flush", 5) == 0) {
281         for (p += 5; ISWHITE(*p); p++)
282             continue;
283         if (*p == '\0')
284             SITEflushall();
285         else if ((sp = SITEfind(p, false)) != NULL)
286             SITEflush(sp);
287         /*else
288             fprintf(stderr, "buffchan flush %s unknown site\n", p);*/
289         return;
290     }
291
292     if (*p == 'd' && strncmp(p, "drop", 4) == 0) {
293         for (p += 4; ISWHITE(*p); p++)
294             continue;
295         if (*p == '\0')
296             SITEcloseall();
297         else if ((sp = SITEfind(p, false)) == NULL)
298             warn("drop %s unknown site", p);
299         else {
300             SITEclose(sp);
301             sp->Dropped = true;
302         }
303         return;
304     }
305
306     if (*p == 'r' && strncmp(p, "readmap", 7) == 0) {
307         MAPread(Map);
308         return;
309     }
310
311     /* Other command messages -- ignored. */
312     warn("unknown message %s", p);
313 }
314
315
316 /*
317 **  Mark that we got a signal; let two signals kill us.
318 */
319 static RETSIGTYPE
320 CATCHinterrupt(int s)
321 {
322     GotInterrupt = true;
323     xsignal(s, SIG_DFL);
324 }
325
326
327 int
328 main(int ac, char *av[])
329 {
330     QIOSTATE    *qp;
331     int i;
332     int Fields;
333     char        *p;
334     char        *next;
335     char        *line;
336     char                *Directory;
337     bool                Redirect;
338     FILE                *F;
339     char                *ERRLOG;
340
341     /* First thing, set up our identity. */
342     message_program_name = "buffchan";
343
344     /* Set defaults. */
345     if (!innconf_read(NULL))
346         exit(1);
347     ERRLOG = concatpath(innconf->pathlog, _PATH_ERRLOG);
348     Directory = NULL;
349     Fields = 1;
350     Format = NULL;
351     Redirect = true;
352     GotInterrupt = false;
353     umask(NEWSUMASK);
354
355     xsignal(SIGHUP, CATCHinterrupt);
356     xsignal(SIGINT, CATCHinterrupt);
357     xsignal(SIGQUIT, CATCHinterrupt);
358     xsignal(SIGPIPE, CATCHinterrupt);
359     xsignal(SIGTERM, CATCHinterrupt);
360     xsignal(SIGALRM, CATCHinterrupt);
361
362     /* Parse JCL. */
363     while ((i = getopt(ac, av, "bc:C:d:f:l:L:m:p:rs:u")) != EOF)
364         switch (i) {
365         default:
366             die("usage error");
367             break;
368         case 'b':
369         case 'u':
370             BufferMode = i;
371             break;
372         case 'c':
373             CloseEvery = atoi(optarg);
374             break;
375         case 'C':
376             CloseSeconds = atoi(optarg);
377             break;
378         case 'd':
379             Directory = optarg;
380             if (Format == NULL)
381                 Format =xstrdup("%s");
382             break;
383         case 'f':
384             Fields = atoi(optarg);
385             break;
386         case 'l':
387             FlushEvery = atoi(optarg);
388             break;
389         case 'L':
390             FlushSeconds = atoi(optarg);
391             break;
392         case 'm':
393             Map = optarg;
394             MAPread(Map);
395             break;
396         case 'p':
397             if ((F = fopen(optarg, "w")) == NULL)
398                 sysdie("cannot fopen %s", optarg);
399             fprintf(F, "%ld\n", (long)getpid());
400             if (ferror(F) || fclose(F) == EOF)
401                 sysdie("cannot fclose %s", optarg);
402             break;
403         case 'r':
404             Redirect = false;
405             break;
406         case 's':
407             Format = optarg;
408             break;
409         }
410     ac -= optind;
411     av += optind;
412     if (ac)
413         die("usage error");
414
415     /* Do some basic set-ups. */
416     if (Redirect)
417         freopen(ERRLOG, "a", stderr);
418     if (Format == NULL) {
419         Format = concatpath(innconf->pathoutgoing, "%s");
420     }
421     if (Directory && chdir(Directory) < 0)
422         sysdie("cannot chdir to %s", Directory);
423     SITEsetup();
424
425     /* Read input. */
426     for (qp = QIOfdopen((int)fileno(stdin)); !GotInterrupt ; ) {
427         if ((line = QIOread(qp)) == NULL) {
428             if (QIOerror(qp)) {
429                 syswarn("cannot read");
430                 break;
431             }
432             if (QIOtoolong(qp)) {
433                 warn("long line");
434                 QIOread(qp);
435                 continue;
436             }
437
438             /* Normal EOF. */
439             break;
440         }
441
442         /* Command? */
443         if (*line == EXP_CONTROL && *++line != EXP_CONTROL) {
444             Process(line);
445             continue;
446         }
447
448         /* Skip the right number of leading fields. */
449         for (i = Fields, p = line; *p; p++)
450             if (*p == ' ' && --i <= 0)
451                 break;
452         if (*p == '\0')
453             /* Nothing to write.  Probably shouldn't happen. */
454             continue;
455
456         /* Add a newline, get the length of all leading fields. */
457         *p++ = '\n';
458         i = p - line;
459
460         if (GetTimeInfo(&Now) < 0) {
461             syswarn("cannot get time");
462             break;
463         }
464
465         /* Rest of the line is space-separated list of filenames. */
466         for (; *p; p = next) {
467             /* Skip whitespace, get next word. */
468             while (*p == ' ')
469                 p++;
470             for (next = p; *next && *next != ' '; next++)
471                 continue;
472             if (*next)
473                 *next++ = '\0';
474
475             SITEwrite(p, line, i);
476         }
477
478     }
479
480     SITEcloseall();
481     exit(0);
482     /* NOTREACHED */
483 }