Commit | Line | Data |
---|---|---|
059ec3d9 PH |
1 | /************************************************* |
2 | * Exim Monitor * | |
3 | *************************************************/ | |
4 | ||
c4ceed07 | 5 | /* Copyright (c) University of Cambridge 1995 - 2012 */ |
059ec3d9 PH |
6 | /* See the file NOTICE for conditions of use and distribution. */ |
7 | ||
8 | ||
9 | #include "em_hdr.h" | |
10 | ||
11 | /* This module contains the main program of the Exim monitor, which | |
12 | sets up the world and then lets the XtAppMainLoop function | |
13 | run things off X events. */ | |
14 | ||
15 | ||
16 | /************************************************* | |
17 | * Static variables * | |
18 | *************************************************/ | |
19 | ||
20 | /* Fallback resources */ | |
21 | ||
22 | static String fallback_resources[] = {"eximon.geometry: +150+0", NULL}; | |
23 | ||
24 | /* X11 fixed argument lists */ | |
25 | ||
26 | static Arg quit_args[] = { | |
27 | {XtNfromVert, (XtArgVal) NULL}, /* must be first */ | |
28 | {XtNlabel, (XtArgVal) " Quit "}, | |
29 | {"left", XawChainLeft}, | |
30 | {"right", XawChainLeft}, | |
31 | {"top", XawChainTop}, | |
32 | {"bottom", XawChainTop} | |
33 | }; | |
34 | ||
35 | static Arg resize_args[] = { | |
36 | {XtNfromVert, (XtArgVal) NULL}, /* must be first */ | |
37 | {XtNfromHoriz,(XtArgVal) NULL}, /* must be second */ | |
38 | {XtNlabel, (XtArgVal) " Size "}, | |
39 | {"left", XawChainLeft}, | |
40 | {"right", XawChainLeft}, | |
41 | {"top", XawChainTop}, | |
42 | {"bottom", XawChainTop} | |
43 | }; | |
44 | ||
45 | static Arg update_args[] = { | |
46 | {XtNfromVert, (XtArgVal) NULL}, /* must be first */ | |
47 | {XtNlabel, (XtArgVal) " Update "}, | |
48 | {"left", XawChainLeft}, | |
49 | {"right", XawChainLeft}, | |
50 | {"top", XawChainTop}, | |
51 | {"bottom", XawChainTop} | |
52 | }; | |
53 | ||
54 | static Arg hide_args[] = { | |
55 | {XtNfromVert, (XtArgVal) NULL}, /* must be first */ | |
56 | {XtNfromHoriz,(XtArgVal) NULL}, /* must be second */ | |
57 | {XtNlabel, (XtArgVal) " Hide "}, | |
58 | {"left", XawChainLeft}, | |
59 | {"right", XawChainLeft}, | |
60 | {"top", XawChainTop}, | |
61 | {"bottom", XawChainTop} | |
62 | }; | |
63 | ||
64 | static Arg unhide_args[] = { | |
65 | {XtNfromVert, (XtArgVal) NULL}, /* must be first */ | |
66 | {XtNfromHoriz,(XtArgVal) NULL}, /* must be second */ | |
67 | {XtNlabel, (XtArgVal) " Unhide "}, | |
68 | {"left", XawChainLeft}, | |
69 | {"right", XawChainLeft}, | |
70 | {"top", XawChainTop}, | |
71 | {"bottom", XawChainTop} | |
72 | }; | |
73 | ||
74 | static Arg log_args[] = { | |
75 | {XtNfromVert, (XtArgVal) NULL}, /* must be first */ | |
76 | {"editType", XawtextEdit}, | |
77 | {"useStringInPlace", (XtArgVal)TRUE}, | |
78 | {"string", (XtArgVal)""}, /* dummy to get it going */ | |
79 | {"scrollVertical", XawtextScrollAlways}, | |
80 | {"scrollHorizontal", XawtextScrollAlways}, | |
81 | {"right", XawChainRight}, | |
82 | {"top", XawChainTop}, | |
83 | {"bottom", XawChainTop} | |
84 | }; | |
85 | ||
86 | static Arg queue_args[] = { | |
87 | {XtNfromVert, (XtArgVal) NULL}, /* must be first */ | |
88 | {"editType", XawtextEdit}, | |
89 | {"string", (XtArgVal)""}, /* dummy to get it going */ | |
90 | {"scrollVertical", XawtextScrollAlways}, | |
91 | {"right", XawChainRight}, | |
92 | {"top", XawChainTop}, | |
93 | {"bottom", XawChainBottom} | |
94 | }; | |
95 | ||
96 | static Arg sizepos_args[] = { | |
97 | {"width", (XtArgVal)NULL}, | |
98 | {"height", (XtArgVal)NULL}, | |
99 | {"x", (XtArgVal)NULL}, | |
100 | {"y", (XtArgVal)NULL} | |
101 | }; | |
102 | ||
103 | XtActionsRec menu_action_table[] = { | |
104 | { "menu-create", menu_create } }; | |
105 | ||
106 | /* Types of non-message dialog action */ | |
107 | ||
108 | enum { da_hide }; | |
109 | ||
110 | /* Miscellaneous local variables */ | |
111 | ||
112 | static int dialog_action; | |
113 | static int tick_stripchart_accumulator = 999999; | |
114 | static int tick_interval = 2; | |
115 | static int maxposset = 0; | |
116 | static int minposset = 0; | |
117 | static int x_adjustment = -1; | |
118 | static int y_adjustment = -1; | |
119 | static Dimension screenwidth, screenheight; | |
120 | static Dimension original_x, original_y; | |
121 | static Dimension maxposx, maxposy; | |
122 | static Dimension minposx, minposy; | |
123 | static Dimension maxwidth, maxheight; | |
124 | static Widget outer_form_widget; | |
125 | static Widget hide_widget; | |
126 | static Widget above_queue_widget; | |
127 | ||
128 | ||
129 | ||
130 | ||
131 | #ifdef STRERROR_FROM_ERRLIST | |
132 | /************************************************* | |
133 | * Provide strerror() for non-ANSI libraries * | |
134 | *************************************************/ | |
135 | ||
136 | /* Some old-fashioned systems still around (e.g. SunOS4) don't have strerror() | |
137 | in their libraries, but can provide the same facility by this simple | |
138 | alternative function. */ | |
139 | ||
140 | uschar * | |
141 | strerror(int n) | |
142 | { | |
143 | if (n < 0 || n >= sys_nerr) return "unknown error number"; | |
144 | return sys_errlist[n]; | |
145 | } | |
146 | #endif /* STRERROR_FROM_ERRLIST */ | |
147 | ||
148 | ||
149 | ||
150 | /************************************************* | |
151 | * Handle attempts to write the log * | |
152 | *************************************************/ | |
153 | ||
154 | /* The message gets written to stderr when log_write() is called from a | |
155 | utility. The message always gets '\n' added on the end of it. These calls come | |
156 | from modules such as store.c when things go drastically wrong (e.g. malloc() | |
157 | failing). In normal use they won't get obeyed. | |
158 | ||
159 | Arguments: | |
160 | selector not relevant when running a utility | |
161 | flags not relevant when running a utility | |
162 | format a printf() format | |
163 | ... arguments for format | |
164 | ||
165 | Returns: nothing | |
166 | */ | |
167 | ||
168 | void | |
ad2f0c19 | 169 | log_write(unsigned int selector, int flags, const char *format, ...) |
059ec3d9 PH |
170 | { |
171 | va_list ap; | |
172 | va_start(ap, format); | |
173 | vfprintf(stderr, format, ap); | |
174 | fprintf(stderr, "\n"); | |
175 | va_end(ap); | |
176 | selector = selector; /* Keep picky compilers happy */ | |
177 | flags = flags; | |
178 | } | |
179 | ||
180 | ||
181 | ||
182 | ||
183 | /************************************************* | |
184 | * Extract port from address string * | |
185 | *************************************************/ | |
186 | ||
187 | /* In the spool file, a host plus port is given as an IP address followed by a | |
188 | dot and a port number. This function decodes this. It is needed by the | |
189 | spool-reading function, and copied here to avoid having to include the whole | |
190 | host.c module. One day the interaction between exim and eximon with regard to | |
191 | included code MUST be tidied up! | |
192 | ||
193 | Argument: | |
194 | address points to the string; if there is a port, the '.' in the string | |
195 | is overwritten with zero to terminate the address | |
196 | ||
197 | Returns: 0 if there is no port, else the port number. | |
198 | */ | |
199 | ||
200 | int | |
7cd1141b | 201 | host_address_extract_port(uschar *address) |
059ec3d9 PH |
202 | { |
203 | int skip = -3; /* Skip 3 dots in IPv4 addresses */ | |
204 | address--; | |
205 | while (*(++address) != 0) | |
206 | { | |
207 | int ch = *address; | |
208 | if (ch == ':') skip = 0; /* Skip 0 dots in IPv6 addresses */ | |
209 | else if (ch == '.' && skip++ >= 0) break; | |
210 | } | |
211 | if (*address == 0) return 0; | |
212 | *address++ = 0; | |
213 | return Uatoi(address); | |
214 | } | |
215 | ||
216 | ||
217 | ||
218 | ||
219 | /************************************************* | |
220 | * SIGCHLD handler * | |
221 | *************************************************/ | |
222 | ||
223 | /* Operations on messages are done in subprocesses; this handler | |
224 | just catches them when they finish. It causes a queue display update | |
225 | unless configured not to. */ | |
226 | ||
227 | static void sigchld_handler(int sig) | |
228 | { | |
229 | while (waitpid(-1, NULL, WNOHANG) > 0); | |
230 | signal(sig, sigchld_handler); | |
231 | if (action_queue_update) tick_queue_accumulator = 999999; | |
232 | } | |
233 | ||
234 | ||
235 | ||
236 | /************************************************* | |
237 | * Callback routines * | |
238 | *************************************************/ | |
239 | ||
240 | ||
241 | void updateAction(Widget w, XtPointer client_data, XtPointer call_data) | |
242 | { | |
243 | w = w; /* Keep picky compilers happy */ | |
244 | client_data = client_data; | |
245 | call_data = call_data; | |
246 | scan_spool_input(TRUE); | |
247 | queue_display(); | |
248 | tick_queue_accumulator = 0; | |
249 | } | |
250 | ||
251 | void hideAction(Widget w, XtPointer client_data, XtPointer call_data) | |
252 | { | |
253 | w = w; /* Keep picky compilers happy */ | |
254 | client_data = client_data; | |
255 | call_data = call_data; | |
256 | actioned_message[0] = 0; | |
257 | dialog_ref_widget = w; | |
258 | dialog_action = da_hide; | |
259 | create_dialog(US"Hide addresses ending with", US""); | |
260 | } | |
261 | ||
262 | void unhideAction(Widget w, XtPointer client_data, XtPointer call_data) | |
263 | { | |
264 | skip_item *sk = queue_skip; | |
265 | ||
266 | w = w; /* Keep picky compilers happy */ | |
267 | client_data = client_data; | |
268 | call_data = call_data; | |
269 | ||
270 | while (sk != NULL) | |
271 | { | |
272 | skip_item *next = sk->next; | |
273 | store_free(sk); | |
274 | sk = next; | |
275 | } | |
276 | queue_skip = NULL; | |
277 | ||
278 | XtDestroyWidget(unhide_widget); | |
279 | unhide_widget = NULL; | |
280 | ||
281 | scan_spool_input(TRUE); | |
282 | queue_display(); | |
283 | tick_queue_accumulator = 0; | |
284 | } | |
285 | ||
286 | void quitAction(Widget w, XtPointer client_data, XtPointer call_data) | |
287 | { | |
288 | w = w; /* Keep picky compilers happy */ | |
289 | client_data = client_data; | |
290 | call_data = call_data; | |
291 | exit(0); | |
292 | } | |
293 | ||
294 | ||
295 | /* Action when the "Size" button is pressed. This is a kludged up mess | |
296 | that I made work after much messing around. Reading the position of the | |
297 | toplevel widget gets the absolute position of the data portion of the window, | |
298 | excluding the window manager's furniture. However, positioning the toplevel | |
299 | widget's window seems to position the top corner of the furniture under the twm | |
300 | window manager, but not under fwvm and others. The two cases are distinguished | |
301 | by the values of x_adjustment and y_adjustment. | |
302 | ||
303 | For twm (adjustment >= 0), one has to fudge the miminizing function to ensure | |
304 | that we go back to exactly the same position as before. | |
305 | ||
306 | For fwvm (adjustment < 0), one has to fudge the "top left hand corner" | |
307 | positioning to ensure that the window manager's furniture gets displayed on the | |
308 | screen. I haven't found a way of discovering the thickness of the furniture, so | |
309 | some screwed-in values are used. | |
310 | ||
311 | This is all ad hoc, developed by floundering around as I haven't found any | |
312 | documentation that tells me what I really should do. */ | |
313 | ||
314 | void resizeAction(Widget button, XtPointer client_data, XtPointer call_data) | |
315 | { | |
316 | Dimension x, y; | |
317 | Dimension width, height; | |
318 | XWindowAttributes a; | |
319 | Window w = XtWindow(toplevel_widget); | |
320 | ||
321 | button = button; /* Keep picky compilers happy */ | |
322 | client_data = client_data; | |
323 | call_data = call_data; | |
324 | ||
325 | /* Get the position and size of the top level widget. */ | |
326 | ||
327 | sizepos_args[0].value = (XtArgVal)(&width); | |
328 | sizepos_args[1].value = (XtArgVal)(&height); | |
329 | sizepos_args[2].value = (XtArgVal)(&x); | |
330 | sizepos_args[3].value = (XtArgVal)(&y); | |
331 | XtGetValues(toplevel_widget, sizepos_args, 4); | |
332 | ||
333 | /* Get the position of the widget's window relative to its parent; this | |
334 | gives the thickness of the window manager's furniture. At least it does | |
335 | in twm. For fwvm it gives zero. The size/movement function uses this data. | |
336 | I tried doing this before entering the main loop, but it didn't always | |
337 | work properly with twm. Running it every time seems to be OK. */ | |
338 | ||
339 | XGetWindowAttributes(X_display, XtWindow(toplevel_widget), &a); | |
340 | if (a.x != 0) x_adjustment = a.x; | |
341 | if (a.y != 0) y_adjustment = a.y; | |
342 | ||
343 | /* If at maximum size, reduce to minimum and move back to where it was | |
344 | when maximized, if that value is set, allowing for the furniture in cases | |
345 | where the positioning includes the furniture. */ | |
346 | ||
347 | if (width == maxwidth && height == maxheight) | |
348 | { | |
349 | maxposx = x; | |
350 | maxposy = y; | |
351 | maxposset = 1; | |
352 | ||
353 | if (minposset) | |
354 | xs_SetValues(toplevel_widget, 4, | |
355 | "width", min_width, | |
356 | "height", min_height, | |
357 | "x", minposx - ((x_adjustment >= 0)? x_adjustment : 0), | |
358 | "y", minposy - ((y_adjustment >= 0)? y_adjustment : 0)); | |
359 | else | |
360 | xs_SetValues(toplevel_widget, 2, | |
361 | "width", min_width, | |
362 | "height", min_height); | |
363 | } | |
364 | ||
365 | /* Else always expand to maximum. If currently at minimum size, remember where | |
366 | it was for coming back. If we don't have a value for the thickness of the | |
367 | furniture, the implication is that the coordinates position the application | |
368 | window, so we can't use (0,0) because that loses the furniture. Use screwed in | |
369 | values that seem to work with fvwm. */ | |
370 | ||
371 | else | |
372 | { | |
373 | int xx = x; | |
374 | int yy = y; | |
375 | ||
376 | if (width == min_width && height == min_height) | |
377 | { | |
378 | minposx = x; | |
379 | minposy = y; | |
380 | minposset = 1; | |
381 | } | |
382 | ||
383 | if ((int)(x + maxwidth) > (int)screenwidth || | |
384 | (int)(y + maxheight + 10) > (int)screenheight) | |
385 | { | |
386 | if (maxposset) | |
387 | { | |
388 | xx = maxposx - ((x_adjustment >= 0)? x_adjustment : 0); | |
389 | yy = maxposy - ((y_adjustment >= 0)? y_adjustment : 0); | |
390 | } | |
391 | else | |
392 | { | |
393 | if ((int)(x + maxwidth) > (int)screenwidth) | |
394 | xx = (x_adjustment >= 0)? 0 : 4; | |
395 | if ((int)(y + maxheight + 10) > (int)screenheight) | |
396 | yy = (y_adjustment >= 0)? 0 : 21; | |
397 | } | |
398 | ||
399 | xs_SetValues(toplevel_widget, 4, | |
400 | "width", maxwidth, | |
401 | "height", maxheight, | |
402 | "x", xx, | |
403 | "y", yy); | |
404 | } | |
405 | ||
406 | else xs_SetValues(toplevel_widget, 2, | |
407 | "width", maxwidth, | |
408 | "height", maxheight); | |
409 | } | |
410 | ||
411 | /* Ensure the window is at the top */ | |
412 | ||
413 | XRaiseWindow(X_display, w); | |
414 | } | |
415 | ||
416 | ||
417 | ||
418 | ||
419 | /************************************************* | |
420 | * Handle input from non-msg dialogue * | |
421 | *************************************************/ | |
422 | ||
423 | /* The various cases here are: hide domain, (no more yet) */ | |
424 | ||
425 | void NonMessageDialogue(uschar *s) | |
426 | { | |
427 | skip_item *sk; | |
428 | ||
429 | switch(dialog_action) | |
430 | { | |
431 | case da_hide: | |
432 | ||
433 | /* Create the unhide button if not present */ | |
434 | ||
435 | if (unhide_widget == NULL) | |
436 | { | |
437 | unhide_args[0].value = (XtArgVal) above_queue_widget; | |
438 | unhide_args[1].value = (XtArgVal) hide_widget; | |
439 | unhide_widget = XtCreateManagedWidget("unhide", commandWidgetClass, | |
440 | outer_form_widget, unhide_args, XtNumber(unhide_args)); | |
441 | XtAddCallback(unhide_widget, "callback", unhideAction, NULL); | |
442 | } | |
443 | ||
444 | /* Add item to skip queue */ | |
445 | ||
446 | sk = (skip_item *)store_malloc(sizeof(skip_item) + Ustrlen(s)); | |
447 | sk->next = queue_skip; | |
448 | queue_skip = sk; | |
449 | Ustrcpy(sk->text, s); | |
450 | sk->reveal = time(NULL) + 60 * 60; | |
451 | scan_spool_input(TRUE); | |
452 | queue_display(); | |
453 | tick_queue_accumulator = 0; | |
454 | break; | |
455 | } | |
456 | } | |
457 | ||
458 | ||
459 | ||
460 | /************************************************* | |
461 | * Ticker function * | |
462 | *************************************************/ | |
463 | ||
464 | /* This function is called initially to set up the starting data | |
465 | values; it then sets a timeout so that it continues to be called | |
466 | every 2 seconds. */ | |
467 | ||
468 | static void ticker(XtPointer pt, XtIntervalId *i) | |
469 | { | |
470 | pipe_item **pp = &pipe_chain; | |
471 | pipe_item *p = pipe_chain; | |
472 | tick_queue_accumulator += tick_interval; | |
473 | tick_stripchart_accumulator += tick_interval; | |
474 | read_log(); | |
475 | ||
476 | pt = pt; /* Keep picky compilers happy */ | |
477 | i = i; | |
478 | ||
479 | /* If we have passed the queue update time, we must do a full | |
480 | scan of the queue, checking for new arrivals, etc. This will | |
481 | as a by-product set the count of items for use by the stripchart | |
482 | display. On some systems, SIGCHLD signals can get lost at busy times, | |
483 | so just in case, clean up any completed children here. */ | |
484 | ||
485 | if (tick_queue_accumulator >= queue_update) | |
486 | { | |
487 | scan_spool_input(TRUE); | |
488 | queue_display(); | |
489 | tick_queue_accumulator = 0; | |
490 | if (tick_stripchart_accumulator >= stripchart_update) | |
491 | tick_stripchart_accumulator = 0; | |
492 | while (waitpid(-1, NULL, WNOHANG) > 0); | |
493 | } | |
494 | ||
495 | /* Otherwise, if we have exceeded the stripchart interval, | |
496 | do a reduced queue scan that simply provides the count for | |
497 | the stripchart. */ | |
498 | ||
499 | else if (tick_stripchart_accumulator >= stripchart_update) | |
500 | { | |
501 | scan_spool_input(FALSE); | |
502 | tick_stripchart_accumulator = 0; | |
503 | } | |
504 | ||
505 | /* Scan any pipes that are set up for listening to delivery processes, | |
506 | and display their output if their windows are still open. */ | |
507 | ||
508 | while (p != NULL) | |
509 | { | |
510 | int count; | |
511 | uschar buffer[256]; | |
512 | ||
513 | while ((count = read(p->fd, buffer, 254)) > 0) | |
514 | { | |
515 | buffer[count] = 0; | |
516 | if (p->widget != NULL) text_show(p->widget, buffer); | |
517 | } | |
518 | ||
519 | if (count == 0) | |
520 | { | |
521 | close(p->fd); | |
522 | *pp = p->next; | |
523 | store_free(p); | |
524 | /* If configured, cause display update */ | |
525 | if (action_queue_update) tick_queue_accumulator = 999999; | |
526 | } | |
527 | ||
528 | else pp = &(p->next); | |
529 | ||
530 | p = *pp; | |
531 | } | |
532 | ||
533 | /* Reset the timer for next time */ | |
534 | ||
535 | XtAppAddTimeOut(X_appcon, tick_interval * 1000, ticker, 0); | |
536 | } | |
537 | ||
538 | ||
539 | ||
540 | /************************************************* | |
541 | * Find Num Lock modifiers * | |
542 | *************************************************/ | |
543 | ||
544 | /* Return a string with the modifiers generated by XK_Num_Lock, or return | |
545 | NULL if XK_Num_Lock doesn't generate any modifiers. This is needed because Num | |
546 | Lock isn't always the same modifier on all servers. | |
547 | ||
548 | Arguments: | |
549 | display the Display | |
550 | buf a buffer in which to put the answers (long enough to hold 5) | |
551 | ||
552 | Returns: points to the buffer, or NULL | |
553 | */ | |
554 | ||
555 | static uschar * | |
556 | numlock_modifiers(Display *display, uschar *buf) | |
557 | { | |
558 | XModifierKeymap *m; | |
559 | int i, j; | |
560 | uschar *ret = NULL; | |
561 | ||
562 | m = XGetModifierMapping(display); | |
563 | if (m == NULL) | |
564 | { | |
565 | printf("Not enough memory\n"); | |
566 | exit (EXIT_FAILURE); | |
567 | } | |
568 | ||
569 | /* Look at Mod1 through Mod5, and fill in the buffer as necessary. */ | |
570 | ||
571 | buf[0] = 0; | |
572 | for (i = 3; i < 8; i++) | |
573 | { | |
574 | for (j = 0; j < m->max_keypermod; j++) | |
575 | { | |
576 | if (XKeycodeToKeysym(display, m->modifiermap [i*m->max_keypermod + j], 0) | |
577 | == XK_Num_Lock) | |
578 | { | |
579 | sprintf(CS(buf+Ustrlen(buf)), " Mod%d", i-2); | |
580 | ret = buf; | |
581 | } | |
582 | } | |
583 | } | |
584 | ||
585 | XFreeModifiermap(m); | |
586 | return ret; | |
587 | } | |
588 | ||
589 | ||
590 | ||
591 | /************************************************* | |
592 | * Initialize * | |
593 | *************************************************/ | |
594 | ||
595 | int main(int argc, char **argv) | |
596 | { | |
597 | int i; | |
598 | struct stat statdata; | |
599 | uschar modbuf[] = " Mod1 Mod2 Mod3 Mod4 Mod5"; | |
600 | uschar *numlock; | |
601 | Widget stripchart_form_widget, | |
602 | update_widget, | |
603 | quit_widget, | |
604 | resize_widget; | |
605 | ||
606 | /* The exim global message_id needs to get set */ | |
607 | ||
608 | message_id_external = message_id_option + 1; | |
609 | message_id = message_id_external + 1; | |
610 | message_subdir[1] = 0; | |
611 | ||
612 | /* Some store needs getting for big_buffer, which is used for | |
613 | constructing file names and things. This call will initialize | |
614 | the store_get() function. */ | |
615 | ||
616 | big_buffer_size = 1024; | |
617 | big_buffer = store_get(big_buffer_size); | |
618 | ||
619 | /* Set up the version string and date and output them */ | |
620 | ||
621 | version_init(); | |
622 | printf("\nExim Monitor version %s (compiled %s) initializing\n", | |
623 | version_string, version_date); | |
624 | ||
625 | /* Initialize various things from the environment and arguments. */ | |
626 | ||
627 | init(argc, USS argv); | |
628 | ||
629 | /* Set up the SIGCHLD handler */ | |
630 | ||
631 | signal(SIGCHLD, sigchld_handler); | |
632 | ||
633 | /* Get the buffer for storing the string for the log display. */ | |
634 | ||
635 | log_display_buffer = (uschar *)store_malloc(log_buffer_size); | |
636 | log_display_buffer[0] = 0; | |
637 | ||
638 | /* Initialize the data structures for the stripcharts */ | |
639 | ||
640 | stripchart_init(); | |
641 | ||
642 | /* If log_file contains the empty string, then Exim is running using syslog | |
643 | only, and we can't tail the log. If not, open the log file and position to the | |
644 | end of it. Before doing so, we have to detect whether the log files are | |
645 | datestamped, and if so, sort out the name. The string in log_file already has | |
f1e5fef5 PP |
646 | %s replaced by "main"; if datestamping is occurring, %D or %M will be present. |
647 | In fact, we don't need to test explicitly - just process the string with | |
059ec3d9 PH |
648 | string_format. |
649 | ||
650 | Once opened, save the file's inode so that we can detect when the file is | |
651 | switched to another one for non-datestamped files. However, allow the monitor | |
652 | to start up without a log file (can happen if no messages have been sent | |
653 | today.) */ | |
654 | ||
655 | if (log_file[0] != 0) | |
656 | { | |
c6e95d22 PP |
657 | /* Do *not* use "%s" here, we need the %D datestamp in the log_file to |
658 | be expanded! */ | |
659 | (void)string_format(log_file_open, sizeof(log_file_open), CS log_file); | |
059ec3d9 PH |
660 | log_datestamping = string_datestamp_offset >= 0; |
661 | ||
662 | LOG = fopen(CS log_file_open, "r"); | |
663 | ||
664 | if (LOG == NULL) | |
665 | { | |
666 | printf("*** eximon warning: can't open log file %s - will try " | |
667 | "periodically\n", log_file_open); | |
668 | } | |
669 | else | |
670 | { | |
671 | fseek(LOG, 0, SEEK_END); | |
672 | log_position = ftell(LOG); | |
673 | fstat(fileno(LOG), &statdata); | |
674 | log_inode = statdata.st_ino; | |
675 | } | |
676 | } | |
677 | else | |
678 | { | |
679 | printf("*** eximon warning: no log file available to tail\n"); | |
680 | } | |
681 | ||
682 | /* Now initialize the X world and create the top-level widget */ | |
683 | ||
684 | toplevel_widget = XtAppInitialize(&X_appcon, "Eximon", NULL, 0, &argc, argv, | |
685 | fallback_resources, NULL, 0); | |
686 | X_display = XtDisplay(toplevel_widget); | |
687 | xs_SetValues(toplevel_widget, 4, | |
688 | "title", window_title, | |
689 | "iconName", window_title, | |
690 | "minWidth", min_width, | |
691 | "minHeight", min_height); | |
692 | ||
693 | ||
694 | /* Create the action for setting up the menu in the queue display | |
695 | window, and register the action for positioning the menu. */ | |
696 | ||
697 | XtAppAddActions(X_appcon, menu_action_table, 1); | |
698 | XawSimpleMenuAddGlobalActions(X_appcon); | |
699 | ||
700 | /* Set up translation tables for the text widgets we use. We don't | |
701 | want all the generality of editing, etc. that the defaults provide. | |
702 | This cannot be done before initializing X - the parser complains | |
703 | about unknown events, modifiers, etc. in an unhelpful way... The | |
704 | queue text widget has a different table which includes the button | |
705 | for popping up the menu. Note that the order of things in these | |
706 | tables is significant. Shift<thing> must come before <thing> as | |
707 | otherwise it isn't noticed. */ | |
708 | ||
709 | /* | |
710 | <FocusIn>: display-caret(on)\n\ | |
711 | <FocusOut>: display-caret(off)\n\ | |
712 | */ | |
713 | ||
714 | /* The translation manager sets up passive grabs for the menu popups as a | |
715 | result of MenuPopup(), but the grabs match only the exact modifiers listed, | |
716 | hence combinations with and without caps-lock and num-lock must be given, | |
717 | rather than just one "Shift<Btn1Down>" (or whatever menu_event is set to), | |
718 | despite the fact that that notation (without a leading !) should ignore the | |
719 | state of other modifiers. Thanks to Kevin Ryde for this information, and for | |
720 | the function above that discovers which modifier is Num Lock, because it turns | |
721 | out that it varies from server to server. */ | |
722 | ||
723 | sprintf(CS big_buffer, | |
724 | "!%s: menu-create() XawPositionSimpleMenu(menu) MenuPopup(menu)\n\ | |
725 | !Lock %s: menu-create() XawPositionSimpleMenu(menu) MenuPopup(menu)\n\ | |
726 | ", menu_event, menu_event); | |
727 | ||
728 | numlock = numlock_modifiers(X_display, modbuf); /* Get Num Lock modifier(s) */ | |
729 | ||
730 | if (numlock != NULL) sprintf(CS big_buffer + Ustrlen(big_buffer), | |
731 | "!%s %s: menu-create() XawPositionSimpleMenu(menu) MenuPopup(menu)\n\ | |
732 | !Lock %s %s: menu-create() XawPositionSimpleMenu(menu) MenuPopup(menu)\n\ | |
733 | ", numlock, menu_event, numlock, menu_event); | |
734 | ||
735 | sprintf(CS big_buffer + Ustrlen(big_buffer), | |
736 | "<Btn1Down>: select-start()\n\ | |
737 | <Btn1Motion>: extend-adjust()\n\ | |
738 | <Btn1Up>: extend-end(PRIMARY,CUT_BUFFER0)\n\ | |
739 | <Btn3Down>: extend-start()\n\ | |
740 | <Btn3Motion>: extend-adjust()\n\ | |
741 | <Btn3Up>: extend-end(PRIMARY,CUT_BUFFER0)\n\ | |
742 | <Key>Up: scroll-one-line-down()\n\ | |
743 | <Key>Down: scroll-one-line-up()\n\ | |
744 | Ctrl<Key>R: search(backward)\n\ | |
745 | Ctrl<Key>S: search(forward)\n\ | |
746 | "); | |
747 | ||
748 | queue_trans = XtParseTranslationTable(CS big_buffer); | |
749 | ||
750 | text_trans = XtParseTranslationTable( | |
751 | "<Btn1Down>: select-start()\n\ | |
752 | <Btn1Motion>: extend-adjust()\n\ | |
753 | <Btn1Up>: extend-end(PRIMARY,CUT_BUFFER0)\n\ | |
754 | <Btn3Down>: extend-start()\n\ | |
755 | <Btn3Motion>: extend-adjust()\n\ | |
756 | <Btn3Up>: extend-end(PRIMARY,CUT_BUFFER0)\n\ | |
757 | <Key>Up: scroll-one-line-down()\n\ | |
758 | <Key>Down: scroll-one-line-up()\n\ | |
759 | Ctrl<Key>R: search(backward)\n\ | |
760 | Ctrl<Key>S: search(forward)\n\ | |
761 | "); | |
762 | ||
763 | ||
764 | /* Create a toplevel form widget to hold all the other things */ | |
765 | ||
766 | outer_form_widget = XtCreateManagedWidget("form", formWidgetClass, | |
767 | toplevel_widget, NULL, 0); | |
768 | ||
769 | /* Now create an inner form to hold the stripcharts */ | |
770 | ||
771 | stripchart_form_widget = XtCreateManagedWidget("form", formWidgetClass, | |
772 | outer_form_widget, NULL, 0); | |
773 | xs_SetValues(stripchart_form_widget, 5, | |
774 | "defaultDistance", 8, | |
775 | "left", XawChainLeft, | |
776 | "right", XawChainLeft, | |
777 | "top", XawChainTop, | |
778 | "bottom", XawChainTop); | |
779 | ||
780 | /* Create the queue count stripchart and its label. */ | |
781 | ||
782 | create_stripchart(stripchart_form_widget, queue_stripchart_name); | |
783 | ||
784 | /* If configured, create the size monitoring stripchart, but | |
785 | only if the OS supports statfs(). */ | |
786 | ||
787 | if (size_stripchart != NULL) | |
788 | { | |
789 | #ifdef HAVE_STATFS | |
790 | if (size_stripchart_name == NULL) | |
791 | { | |
792 | size_stripchart_name = size_stripchart + Ustrlen(size_stripchart) - 1; | |
793 | while (size_stripchart_name > size_stripchart && | |
794 | *size_stripchart_name == '/') size_stripchart_name--; | |
795 | while (size_stripchart_name > size_stripchart && | |
796 | *size_stripchart_name != '/') size_stripchart_name--; | |
797 | } | |
798 | create_stripchart(stripchart_form_widget, size_stripchart_name); | |
799 | #else | |
800 | printf("Can't create size stripchart: statfs() function not available\n"); | |
801 | #endif | |
802 | } | |
803 | ||
804 | /* Now create the configured input/output stripcharts; note | |
805 | the total number includes the queue stripchart. */ | |
806 | ||
807 | for (i = stripchart_varstart; i < stripchart_number; i++) | |
808 | create_stripchart(stripchart_form_widget, stripchart_title[i]); | |
809 | ||
810 | /* Next in vertical order come the Resize & Quit buttons */ | |
811 | ||
812 | quit_args[0].value = (XtArgVal) stripchart_form_widget; | |
813 | quit_widget = XtCreateManagedWidget("quit", commandWidgetClass, | |
814 | outer_form_widget, quit_args, XtNumber(quit_args)); | |
815 | XtAddCallback(quit_widget, "callback", quitAction, NULL); | |
816 | ||
817 | resize_args[0].value = (XtArgVal) stripchart_form_widget; | |
818 | resize_args[1].value = (XtArgVal) quit_widget; | |
819 | resize_widget = XtCreateManagedWidget("resize", commandWidgetClass, | |
820 | outer_form_widget, resize_args, XtNumber(resize_args)); | |
821 | XtAddCallback(resize_widget, "callback", resizeAction, NULL); | |
822 | ||
823 | /* In the absence of log tailing, the quit widget is the one above the | |
824 | queue listing. */ | |
825 | ||
826 | above_queue_widget = quit_widget; | |
827 | ||
828 | /* Create an Ascii text widget for the log tail display if we are tailing a | |
829 | log. Skip it if not. */ | |
830 | ||
831 | if (log_file[0] != 0) | |
832 | { | |
833 | log_args[0].value = (XtArgVal) quit_widget; | |
834 | log_widget = XtCreateManagedWidget("log", asciiTextWidgetClass, | |
835 | outer_form_widget, log_args, XtNumber(log_args)); | |
836 | XawTextDisplayCaret(log_widget, TRUE); | |
837 | xs_SetValues(log_widget, 6, | |
838 | "editType", XawtextEdit, | |
839 | "translations", text_trans, | |
840 | "string", log_display_buffer, | |
841 | "length", log_buffer_size, | |
842 | "height", log_depth, | |
843 | "width", log_width); | |
844 | ||
845 | if (log_font != NULL) | |
846 | { | |
847 | XFontStruct *f = XLoadQueryFont(X_display, CS log_font); | |
848 | if (f != NULL) xs_SetValues(log_widget, 1, "font", f); | |
849 | } | |
850 | ||
851 | above_queue_widget = log_widget; | |
852 | } | |
853 | ||
854 | /* The update button */ | |
855 | ||
856 | update_args[0].value = (XtArgVal) above_queue_widget; | |
857 | update_widget = XtCreateManagedWidget("update", commandWidgetClass, | |
858 | outer_form_widget, update_args, XtNumber(update_args)); | |
859 | XtAddCallback(update_widget, "callback", updateAction, NULL); | |
860 | ||
861 | /* The hide button */ | |
862 | ||
863 | hide_args[0].value = (XtArgVal) above_queue_widget; | |
864 | hide_args[1].value = (XtArgVal) update_widget; | |
865 | hide_widget = XtCreateManagedWidget("hide", commandWidgetClass, | |
866 | outer_form_widget, hide_args, XtNumber(hide_args)); | |
867 | XtAddCallback(hide_widget, "callback", hideAction, NULL); | |
868 | ||
869 | /* Create an Ascii text widget for the queue display. */ | |
870 | ||
871 | queue_args[0].value = (XtArgVal) update_widget; | |
872 | queue_widget = XtCreateManagedWidget("queue", asciiTextWidgetClass, | |
873 | outer_form_widget, queue_args, XtNumber(queue_args)); | |
874 | XawTextDisplayCaret(queue_widget, TRUE); | |
875 | ||
876 | xs_SetValues(queue_widget, 4, | |
877 | "editType", XawtextEdit, | |
878 | "height", queue_depth, | |
879 | "width", queue_width, | |
880 | "translations", queue_trans); | |
881 | ||
882 | if (queue_font != NULL) | |
883 | { | |
884 | XFontStruct *f = XLoadQueryFont(X_display, CS queue_font); | |
885 | if (f != NULL) xs_SetValues(queue_widget, 1, "font", f); | |
886 | } | |
887 | ||
888 | /* Call the ticker function to get the initial data set up. It | |
889 | arranges to have itself recalled every 2 seconds. */ | |
890 | ||
891 | ticker(NULL, NULL); | |
892 | ||
893 | /* Everything is now set up; this flag is used by the regerror | |
894 | function and also by the queue reader. */ | |
895 | ||
896 | eximon_initialized = TRUE; | |
897 | printf("\nExim Monitor running\n"); | |
898 | ||
899 | /* Realize the toplevel and thereby get things displayed */ | |
900 | ||
901 | XtRealizeWidget(toplevel_widget); | |
902 | ||
903 | /* Find out the size of the initial window, and set that as its | |
904 | maximum. While we are at it, get the initial position. */ | |
905 | ||
906 | sizepos_args[0].value = (XtArgVal)(&maxwidth); | |
907 | sizepos_args[1].value = (XtArgVal)(&maxheight); | |
908 | sizepos_args[2].value = (XtArgVal)(&original_x); | |
909 | sizepos_args[3].value = (XtArgVal)(&original_y); | |
910 | XtGetValues(toplevel_widget, sizepos_args, 4); | |
911 | ||
912 | xs_SetValues(toplevel_widget, 2, | |
913 | "maxWidth", maxwidth, | |
914 | "maxHeight", maxheight); | |
915 | ||
916 | /* Set up the size of the screen */ | |
917 | ||
918 | screenwidth = XDisplayWidth(X_display, 0); | |
919 | screenheight= XDisplayHeight(X_display,0); | |
920 | ||
921 | /* Register the action table */ | |
922 | ||
923 | XtAppAddActions(X_appcon, actionTable, actionTableSize); | |
924 | ||
925 | /* Reduce the window to the small size if this is wanted */ | |
926 | ||
927 | if (start_small) resizeAction(NULL, NULL, NULL); | |
928 | ||
929 | /* Enter the application loop which handles things from here | |
930 | onwards. The return statement is never obeyed, but is needed to | |
931 | keep pedantic ANSI compilers happy. */ | |
932 | ||
933 | XtAppMainLoop(X_appcon); | |
934 | ||
935 | return 0; | |
936 | } | |
937 | ||
938 | /* End of em_main.c */ | |
939 |