Commit | Line | Data |
---|---|---|
184e8823 | 1 | /* $Cambridge: exim/src/exim_monitor/em_main.c,v 1.5 2007/01/08 10:50:17 ph10 Exp $ */ |
059ec3d9 PH |
2 | |
3 | /************************************************* | |
4 | * Exim Monitor * | |
5 | *************************************************/ | |
6 | ||
184e8823 | 7 | /* Copyright (c) University of Cambridge 1995 - 2007 */ |
059ec3d9 PH |
8 | /* See the file NOTICE for conditions of use and distribution. */ |
9 | ||
10 | ||
11 | #include "em_hdr.h" | |
12 | ||
13 | /* This module contains the main program of the Exim monitor, which | |
14 | sets up the world and then lets the XtAppMainLoop function | |
15 | run things off X events. */ | |
16 | ||
17 | ||
18 | /************************************************* | |
19 | * Static variables * | |
20 | *************************************************/ | |
21 | ||
22 | /* Fallback resources */ | |
23 | ||
24 | static String fallback_resources[] = {"eximon.geometry: +150+0", NULL}; | |
25 | ||
26 | /* X11 fixed argument lists */ | |
27 | ||
28 | static Arg quit_args[] = { | |
29 | {XtNfromVert, (XtArgVal) NULL}, /* must be first */ | |
30 | {XtNlabel, (XtArgVal) " Quit "}, | |
31 | {"left", XawChainLeft}, | |
32 | {"right", XawChainLeft}, | |
33 | {"top", XawChainTop}, | |
34 | {"bottom", XawChainTop} | |
35 | }; | |
36 | ||
37 | static Arg resize_args[] = { | |
38 | {XtNfromVert, (XtArgVal) NULL}, /* must be first */ | |
39 | {XtNfromHoriz,(XtArgVal) NULL}, /* must be second */ | |
40 | {XtNlabel, (XtArgVal) " Size "}, | |
41 | {"left", XawChainLeft}, | |
42 | {"right", XawChainLeft}, | |
43 | {"top", XawChainTop}, | |
44 | {"bottom", XawChainTop} | |
45 | }; | |
46 | ||
47 | static Arg update_args[] = { | |
48 | {XtNfromVert, (XtArgVal) NULL}, /* must be first */ | |
49 | {XtNlabel, (XtArgVal) " Update "}, | |
50 | {"left", XawChainLeft}, | |
51 | {"right", XawChainLeft}, | |
52 | {"top", XawChainTop}, | |
53 | {"bottom", XawChainTop} | |
54 | }; | |
55 | ||
56 | static Arg hide_args[] = { | |
57 | {XtNfromVert, (XtArgVal) NULL}, /* must be first */ | |
58 | {XtNfromHoriz,(XtArgVal) NULL}, /* must be second */ | |
59 | {XtNlabel, (XtArgVal) " Hide "}, | |
60 | {"left", XawChainLeft}, | |
61 | {"right", XawChainLeft}, | |
62 | {"top", XawChainTop}, | |
63 | {"bottom", XawChainTop} | |
64 | }; | |
65 | ||
66 | static Arg unhide_args[] = { | |
67 | {XtNfromVert, (XtArgVal) NULL}, /* must be first */ | |
68 | {XtNfromHoriz,(XtArgVal) NULL}, /* must be second */ | |
69 | {XtNlabel, (XtArgVal) " Unhide "}, | |
70 | {"left", XawChainLeft}, | |
71 | {"right", XawChainLeft}, | |
72 | {"top", XawChainTop}, | |
73 | {"bottom", XawChainTop} | |
74 | }; | |
75 | ||
76 | static Arg log_args[] = { | |
77 | {XtNfromVert, (XtArgVal) NULL}, /* must be first */ | |
78 | {"editType", XawtextEdit}, | |
79 | {"useStringInPlace", (XtArgVal)TRUE}, | |
80 | {"string", (XtArgVal)""}, /* dummy to get it going */ | |
81 | {"scrollVertical", XawtextScrollAlways}, | |
82 | {"scrollHorizontal", XawtextScrollAlways}, | |
83 | {"right", XawChainRight}, | |
84 | {"top", XawChainTop}, | |
85 | {"bottom", XawChainTop} | |
86 | }; | |
87 | ||
88 | static Arg queue_args[] = { | |
89 | {XtNfromVert, (XtArgVal) NULL}, /* must be first */ | |
90 | {"editType", XawtextEdit}, | |
91 | {"string", (XtArgVal)""}, /* dummy to get it going */ | |
92 | {"scrollVertical", XawtextScrollAlways}, | |
93 | {"right", XawChainRight}, | |
94 | {"top", XawChainTop}, | |
95 | {"bottom", XawChainBottom} | |
96 | }; | |
97 | ||
98 | static Arg sizepos_args[] = { | |
99 | {"width", (XtArgVal)NULL}, | |
100 | {"height", (XtArgVal)NULL}, | |
101 | {"x", (XtArgVal)NULL}, | |
102 | {"y", (XtArgVal)NULL} | |
103 | }; | |
104 | ||
105 | XtActionsRec menu_action_table[] = { | |
106 | { "menu-create", menu_create } }; | |
107 | ||
108 | /* Types of non-message dialog action */ | |
109 | ||
110 | enum { da_hide }; | |
111 | ||
112 | /* Miscellaneous local variables */ | |
113 | ||
114 | static int dialog_action; | |
115 | static int tick_stripchart_accumulator = 999999; | |
116 | static int tick_interval = 2; | |
117 | static int maxposset = 0; | |
118 | static int minposset = 0; | |
119 | static int x_adjustment = -1; | |
120 | static int y_adjustment = -1; | |
121 | static Dimension screenwidth, screenheight; | |
122 | static Dimension original_x, original_y; | |
123 | static Dimension maxposx, maxposy; | |
124 | static Dimension minposx, minposy; | |
125 | static Dimension maxwidth, maxheight; | |
126 | static Widget outer_form_widget; | |
127 | static Widget hide_widget; | |
128 | static Widget above_queue_widget; | |
129 | ||
130 | ||
131 | ||
132 | ||
133 | #ifdef STRERROR_FROM_ERRLIST | |
134 | /************************************************* | |
135 | * Provide strerror() for non-ANSI libraries * | |
136 | *************************************************/ | |
137 | ||
138 | /* Some old-fashioned systems still around (e.g. SunOS4) don't have strerror() | |
139 | in their libraries, but can provide the same facility by this simple | |
140 | alternative function. */ | |
141 | ||
142 | uschar * | |
143 | strerror(int n) | |
144 | { | |
145 | if (n < 0 || n >= sys_nerr) return "unknown error number"; | |
146 | return sys_errlist[n]; | |
147 | } | |
148 | #endif /* STRERROR_FROM_ERRLIST */ | |
149 | ||
150 | ||
151 | ||
152 | /************************************************* | |
153 | * Handle attempts to write the log * | |
154 | *************************************************/ | |
155 | ||
156 | /* The message gets written to stderr when log_write() is called from a | |
157 | utility. The message always gets '\n' added on the end of it. These calls come | |
158 | from modules such as store.c when things go drastically wrong (e.g. malloc() | |
159 | failing). In normal use they won't get obeyed. | |
160 | ||
161 | Arguments: | |
162 | selector not relevant when running a utility | |
163 | flags not relevant when running a utility | |
164 | format a printf() format | |
165 | ... arguments for format | |
166 | ||
167 | Returns: nothing | |
168 | */ | |
169 | ||
170 | void | |
171 | log_write(unsigned int selector, int flags, char *format, ...) | |
172 | { | |
173 | va_list ap; | |
174 | va_start(ap, format); | |
175 | vfprintf(stderr, format, ap); | |
176 | fprintf(stderr, "\n"); | |
177 | va_end(ap); | |
178 | selector = selector; /* Keep picky compilers happy */ | |
179 | flags = flags; | |
180 | } | |
181 | ||
182 | ||
183 | ||
184 | ||
185 | /************************************************* | |
186 | * Extract port from address string * | |
187 | *************************************************/ | |
188 | ||
189 | /* In the spool file, a host plus port is given as an IP address followed by a | |
190 | dot and a port number. This function decodes this. It is needed by the | |
191 | spool-reading function, and copied here to avoid having to include the whole | |
192 | host.c module. One day the interaction between exim and eximon with regard to | |
193 | included code MUST be tidied up! | |
194 | ||
195 | Argument: | |
196 | address points to the string; if there is a port, the '.' in the string | |
197 | is overwritten with zero to terminate the address | |
198 | ||
199 | Returns: 0 if there is no port, else the port number. | |
200 | */ | |
201 | ||
202 | int | |
7cd1141b | 203 | host_address_extract_port(uschar *address) |
059ec3d9 PH |
204 | { |
205 | int skip = -3; /* Skip 3 dots in IPv4 addresses */ | |
206 | address--; | |
207 | while (*(++address) != 0) | |
208 | { | |
209 | int ch = *address; | |
210 | if (ch == ':') skip = 0; /* Skip 0 dots in IPv6 addresses */ | |
211 | else if (ch == '.' && skip++ >= 0) break; | |
212 | } | |
213 | if (*address == 0) return 0; | |
214 | *address++ = 0; | |
215 | return Uatoi(address); | |
216 | } | |
217 | ||
218 | ||
219 | ||
220 | ||
221 | /************************************************* | |
222 | * SIGCHLD handler * | |
223 | *************************************************/ | |
224 | ||
225 | /* Operations on messages are done in subprocesses; this handler | |
226 | just catches them when they finish. It causes a queue display update | |
227 | unless configured not to. */ | |
228 | ||
229 | static void sigchld_handler(int sig) | |
230 | { | |
231 | while (waitpid(-1, NULL, WNOHANG) > 0); | |
232 | signal(sig, sigchld_handler); | |
233 | if (action_queue_update) tick_queue_accumulator = 999999; | |
234 | } | |
235 | ||
236 | ||
237 | ||
238 | /************************************************* | |
239 | * Callback routines * | |
240 | *************************************************/ | |
241 | ||
242 | ||
243 | void updateAction(Widget w, XtPointer client_data, XtPointer call_data) | |
244 | { | |
245 | w = w; /* Keep picky compilers happy */ | |
246 | client_data = client_data; | |
247 | call_data = call_data; | |
248 | scan_spool_input(TRUE); | |
249 | queue_display(); | |
250 | tick_queue_accumulator = 0; | |
251 | } | |
252 | ||
253 | void hideAction(Widget w, XtPointer client_data, XtPointer call_data) | |
254 | { | |
255 | w = w; /* Keep picky compilers happy */ | |
256 | client_data = client_data; | |
257 | call_data = call_data; | |
258 | actioned_message[0] = 0; | |
259 | dialog_ref_widget = w; | |
260 | dialog_action = da_hide; | |
261 | create_dialog(US"Hide addresses ending with", US""); | |
262 | } | |
263 | ||
264 | void unhideAction(Widget w, XtPointer client_data, XtPointer call_data) | |
265 | { | |
266 | skip_item *sk = queue_skip; | |
267 | ||
268 | w = w; /* Keep picky compilers happy */ | |
269 | client_data = client_data; | |
270 | call_data = call_data; | |
271 | ||
272 | while (sk != NULL) | |
273 | { | |
274 | skip_item *next = sk->next; | |
275 | store_free(sk); | |
276 | sk = next; | |
277 | } | |
278 | queue_skip = NULL; | |
279 | ||
280 | XtDestroyWidget(unhide_widget); | |
281 | unhide_widget = NULL; | |
282 | ||
283 | scan_spool_input(TRUE); | |
284 | queue_display(); | |
285 | tick_queue_accumulator = 0; | |
286 | } | |
287 | ||
288 | void quitAction(Widget w, XtPointer client_data, XtPointer call_data) | |
289 | { | |
290 | w = w; /* Keep picky compilers happy */ | |
291 | client_data = client_data; | |
292 | call_data = call_data; | |
293 | exit(0); | |
294 | } | |
295 | ||
296 | ||
297 | /* Action when the "Size" button is pressed. This is a kludged up mess | |
298 | that I made work after much messing around. Reading the position of the | |
299 | toplevel widget gets the absolute position of the data portion of the window, | |
300 | excluding the window manager's furniture. However, positioning the toplevel | |
301 | widget's window seems to position the top corner of the furniture under the twm | |
302 | window manager, but not under fwvm and others. The two cases are distinguished | |
303 | by the values of x_adjustment and y_adjustment. | |
304 | ||
305 | For twm (adjustment >= 0), one has to fudge the miminizing function to ensure | |
306 | that we go back to exactly the same position as before. | |
307 | ||
308 | For fwvm (adjustment < 0), one has to fudge the "top left hand corner" | |
309 | positioning to ensure that the window manager's furniture gets displayed on the | |
310 | screen. I haven't found a way of discovering the thickness of the furniture, so | |
311 | some screwed-in values are used. | |
312 | ||
313 | This is all ad hoc, developed by floundering around as I haven't found any | |
314 | documentation that tells me what I really should do. */ | |
315 | ||
316 | void resizeAction(Widget button, XtPointer client_data, XtPointer call_data) | |
317 | { | |
318 | Dimension x, y; | |
319 | Dimension width, height; | |
320 | XWindowAttributes a; | |
321 | Window w = XtWindow(toplevel_widget); | |
322 | ||
323 | button = button; /* Keep picky compilers happy */ | |
324 | client_data = client_data; | |
325 | call_data = call_data; | |
326 | ||
327 | /* Get the position and size of the top level widget. */ | |
328 | ||
329 | sizepos_args[0].value = (XtArgVal)(&width); | |
330 | sizepos_args[1].value = (XtArgVal)(&height); | |
331 | sizepos_args[2].value = (XtArgVal)(&x); | |
332 | sizepos_args[3].value = (XtArgVal)(&y); | |
333 | XtGetValues(toplevel_widget, sizepos_args, 4); | |
334 | ||
335 | /* Get the position of the widget's window relative to its parent; this | |
336 | gives the thickness of the window manager's furniture. At least it does | |
337 | in twm. For fwvm it gives zero. The size/movement function uses this data. | |
338 | I tried doing this before entering the main loop, but it didn't always | |
339 | work properly with twm. Running it every time seems to be OK. */ | |
340 | ||
341 | XGetWindowAttributes(X_display, XtWindow(toplevel_widget), &a); | |
342 | if (a.x != 0) x_adjustment = a.x; | |
343 | if (a.y != 0) y_adjustment = a.y; | |
344 | ||
345 | /* If at maximum size, reduce to minimum and move back to where it was | |
346 | when maximized, if that value is set, allowing for the furniture in cases | |
347 | where the positioning includes the furniture. */ | |
348 | ||
349 | if (width == maxwidth && height == maxheight) | |
350 | { | |
351 | maxposx = x; | |
352 | maxposy = y; | |
353 | maxposset = 1; | |
354 | ||
355 | if (minposset) | |
356 | xs_SetValues(toplevel_widget, 4, | |
357 | "width", min_width, | |
358 | "height", min_height, | |
359 | "x", minposx - ((x_adjustment >= 0)? x_adjustment : 0), | |
360 | "y", minposy - ((y_adjustment >= 0)? y_adjustment : 0)); | |
361 | else | |
362 | xs_SetValues(toplevel_widget, 2, | |
363 | "width", min_width, | |
364 | "height", min_height); | |
365 | } | |
366 | ||
367 | /* Else always expand to maximum. If currently at minimum size, remember where | |
368 | it was for coming back. If we don't have a value for the thickness of the | |
369 | furniture, the implication is that the coordinates position the application | |
370 | window, so we can't use (0,0) because that loses the furniture. Use screwed in | |
371 | values that seem to work with fvwm. */ | |
372 | ||
373 | else | |
374 | { | |
375 | int xx = x; | |
376 | int yy = y; | |
377 | ||
378 | if (width == min_width && height == min_height) | |
379 | { | |
380 | minposx = x; | |
381 | minposy = y; | |
382 | minposset = 1; | |
383 | } | |
384 | ||
385 | if ((int)(x + maxwidth) > (int)screenwidth || | |
386 | (int)(y + maxheight + 10) > (int)screenheight) | |
387 | { | |
388 | if (maxposset) | |
389 | { | |
390 | xx = maxposx - ((x_adjustment >= 0)? x_adjustment : 0); | |
391 | yy = maxposy - ((y_adjustment >= 0)? y_adjustment : 0); | |
392 | } | |
393 | else | |
394 | { | |
395 | if ((int)(x + maxwidth) > (int)screenwidth) | |
396 | xx = (x_adjustment >= 0)? 0 : 4; | |
397 | if ((int)(y + maxheight + 10) > (int)screenheight) | |
398 | yy = (y_adjustment >= 0)? 0 : 21; | |
399 | } | |
400 | ||
401 | xs_SetValues(toplevel_widget, 4, | |
402 | "width", maxwidth, | |
403 | "height", maxheight, | |
404 | "x", xx, | |
405 | "y", yy); | |
406 | } | |
407 | ||
408 | else xs_SetValues(toplevel_widget, 2, | |
409 | "width", maxwidth, | |
410 | "height", maxheight); | |
411 | } | |
412 | ||
413 | /* Ensure the window is at the top */ | |
414 | ||
415 | XRaiseWindow(X_display, w); | |
416 | } | |
417 | ||
418 | ||
419 | ||
420 | ||
421 | /************************************************* | |
422 | * Handle input from non-msg dialogue * | |
423 | *************************************************/ | |
424 | ||
425 | /* The various cases here are: hide domain, (no more yet) */ | |
426 | ||
427 | void NonMessageDialogue(uschar *s) | |
428 | { | |
429 | skip_item *sk; | |
430 | ||
431 | switch(dialog_action) | |
432 | { | |
433 | case da_hide: | |
434 | ||
435 | /* Create the unhide button if not present */ | |
436 | ||
437 | if (unhide_widget == NULL) | |
438 | { | |
439 | unhide_args[0].value = (XtArgVal) above_queue_widget; | |
440 | unhide_args[1].value = (XtArgVal) hide_widget; | |
441 | unhide_widget = XtCreateManagedWidget("unhide", commandWidgetClass, | |
442 | outer_form_widget, unhide_args, XtNumber(unhide_args)); | |
443 | XtAddCallback(unhide_widget, "callback", unhideAction, NULL); | |
444 | } | |
445 | ||
446 | /* Add item to skip queue */ | |
447 | ||
448 | sk = (skip_item *)store_malloc(sizeof(skip_item) + Ustrlen(s)); | |
449 | sk->next = queue_skip; | |
450 | queue_skip = sk; | |
451 | Ustrcpy(sk->text, s); | |
452 | sk->reveal = time(NULL) + 60 * 60; | |
453 | scan_spool_input(TRUE); | |
454 | queue_display(); | |
455 | tick_queue_accumulator = 0; | |
456 | break; | |
457 | } | |
458 | } | |
459 | ||
460 | ||
461 | ||
462 | /************************************************* | |
463 | * Ticker function * | |
464 | *************************************************/ | |
465 | ||
466 | /* This function is called initially to set up the starting data | |
467 | values; it then sets a timeout so that it continues to be called | |
468 | every 2 seconds. */ | |
469 | ||
470 | static void ticker(XtPointer pt, XtIntervalId *i) | |
471 | { | |
472 | pipe_item **pp = &pipe_chain; | |
473 | pipe_item *p = pipe_chain; | |
474 | tick_queue_accumulator += tick_interval; | |
475 | tick_stripchart_accumulator += tick_interval; | |
476 | read_log(); | |
477 | ||
478 | pt = pt; /* Keep picky compilers happy */ | |
479 | i = i; | |
480 | ||
481 | /* If we have passed the queue update time, we must do a full | |
482 | scan of the queue, checking for new arrivals, etc. This will | |
483 | as a by-product set the count of items for use by the stripchart | |
484 | display. On some systems, SIGCHLD signals can get lost at busy times, | |
485 | so just in case, clean up any completed children here. */ | |
486 | ||
487 | if (tick_queue_accumulator >= queue_update) | |
488 | { | |
489 | scan_spool_input(TRUE); | |
490 | queue_display(); | |
491 | tick_queue_accumulator = 0; | |
492 | if (tick_stripchart_accumulator >= stripchart_update) | |
493 | tick_stripchart_accumulator = 0; | |
494 | while (waitpid(-1, NULL, WNOHANG) > 0); | |
495 | } | |
496 | ||
497 | /* Otherwise, if we have exceeded the stripchart interval, | |
498 | do a reduced queue scan that simply provides the count for | |
499 | the stripchart. */ | |
500 | ||
501 | else if (tick_stripchart_accumulator >= stripchart_update) | |
502 | { | |
503 | scan_spool_input(FALSE); | |
504 | tick_stripchart_accumulator = 0; | |
505 | } | |
506 | ||
507 | /* Scan any pipes that are set up for listening to delivery processes, | |
508 | and display their output if their windows are still open. */ | |
509 | ||
510 | while (p != NULL) | |
511 | { | |
512 | int count; | |
513 | uschar buffer[256]; | |
514 | ||
515 | while ((count = read(p->fd, buffer, 254)) > 0) | |
516 | { | |
517 | buffer[count] = 0; | |
518 | if (p->widget != NULL) text_show(p->widget, buffer); | |
519 | } | |
520 | ||
521 | if (count == 0) | |
522 | { | |
523 | close(p->fd); | |
524 | *pp = p->next; | |
525 | store_free(p); | |
526 | /* If configured, cause display update */ | |
527 | if (action_queue_update) tick_queue_accumulator = 999999; | |
528 | } | |
529 | ||
530 | else pp = &(p->next); | |
531 | ||
532 | p = *pp; | |
533 | } | |
534 | ||
535 | /* Reset the timer for next time */ | |
536 | ||
537 | XtAppAddTimeOut(X_appcon, tick_interval * 1000, ticker, 0); | |
538 | } | |
539 | ||
540 | ||
541 | ||
542 | /************************************************* | |
543 | * Find Num Lock modifiers * | |
544 | *************************************************/ | |
545 | ||
546 | /* Return a string with the modifiers generated by XK_Num_Lock, or return | |
547 | NULL if XK_Num_Lock doesn't generate any modifiers. This is needed because Num | |
548 | Lock isn't always the same modifier on all servers. | |
549 | ||
550 | Arguments: | |
551 | display the Display | |
552 | buf a buffer in which to put the answers (long enough to hold 5) | |
553 | ||
554 | Returns: points to the buffer, or NULL | |
555 | */ | |
556 | ||
557 | static uschar * | |
558 | numlock_modifiers(Display *display, uschar *buf) | |
559 | { | |
560 | XModifierKeymap *m; | |
561 | int i, j; | |
562 | uschar *ret = NULL; | |
563 | ||
564 | m = XGetModifierMapping(display); | |
565 | if (m == NULL) | |
566 | { | |
567 | printf("Not enough memory\n"); | |
568 | exit (EXIT_FAILURE); | |
569 | } | |
570 | ||
571 | /* Look at Mod1 through Mod5, and fill in the buffer as necessary. */ | |
572 | ||
573 | buf[0] = 0; | |
574 | for (i = 3; i < 8; i++) | |
575 | { | |
576 | for (j = 0; j < m->max_keypermod; j++) | |
577 | { | |
578 | if (XKeycodeToKeysym(display, m->modifiermap [i*m->max_keypermod + j], 0) | |
579 | == XK_Num_Lock) | |
580 | { | |
581 | sprintf(CS(buf+Ustrlen(buf)), " Mod%d", i-2); | |
582 | ret = buf; | |
583 | } | |
584 | } | |
585 | } | |
586 | ||
587 | XFreeModifiermap(m); | |
588 | return ret; | |
589 | } | |
590 | ||
591 | ||
592 | ||
593 | /************************************************* | |
594 | * Initialize * | |
595 | *************************************************/ | |
596 | ||
597 | int main(int argc, char **argv) | |
598 | { | |
599 | int i; | |
600 | struct stat statdata; | |
601 | uschar modbuf[] = " Mod1 Mod2 Mod3 Mod4 Mod5"; | |
602 | uschar *numlock; | |
603 | Widget stripchart_form_widget, | |
604 | update_widget, | |
605 | quit_widget, | |
606 | resize_widget; | |
607 | ||
608 | /* The exim global message_id needs to get set */ | |
609 | ||
610 | message_id_external = message_id_option + 1; | |
611 | message_id = message_id_external + 1; | |
612 | message_subdir[1] = 0; | |
613 | ||
614 | /* Some store needs getting for big_buffer, which is used for | |
615 | constructing file names and things. This call will initialize | |
616 | the store_get() function. */ | |
617 | ||
618 | big_buffer_size = 1024; | |
619 | big_buffer = store_get(big_buffer_size); | |
620 | ||
621 | /* Set up the version string and date and output them */ | |
622 | ||
623 | version_init(); | |
624 | printf("\nExim Monitor version %s (compiled %s) initializing\n", | |
625 | version_string, version_date); | |
626 | ||
627 | /* Initialize various things from the environment and arguments. */ | |
628 | ||
629 | init(argc, USS argv); | |
630 | ||
631 | /* Set up the SIGCHLD handler */ | |
632 | ||
633 | signal(SIGCHLD, sigchld_handler); | |
634 | ||
635 | /* Get the buffer for storing the string for the log display. */ | |
636 | ||
637 | log_display_buffer = (uschar *)store_malloc(log_buffer_size); | |
638 | log_display_buffer[0] = 0; | |
639 | ||
640 | /* Initialize the data structures for the stripcharts */ | |
641 | ||
642 | stripchart_init(); | |
643 | ||
644 | /* If log_file contains the empty string, then Exim is running using syslog | |
645 | only, and we can't tail the log. If not, open the log file and position to the | |
646 | end of it. Before doing so, we have to detect whether the log files are | |
647 | datestamped, and if so, sort out the name. The string in log_file already has | |
648 | %s replaced by "main"; if datestamping is occurring, %D will be present. In | |
649 | fact, we don't need to test explicitly - just process the string with | |
650 | string_format. | |
651 | ||
652 | Once opened, save the file's inode so that we can detect when the file is | |
653 | switched to another one for non-datestamped files. However, allow the monitor | |
654 | to start up without a log file (can happen if no messages have been sent | |
655 | today.) */ | |
656 | ||
657 | if (log_file[0] != 0) | |
658 | { | |
659 | (void)string_format(log_file_open, sizeof(log_file_open), CS log_file); | |
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 |