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