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