| 1 | /* $Cambridge: exim/src/exim_monitor/em_main.c,v 1.2 2005/01/04 10:00:42 ph10 Exp $ */ |
| 2 | |
| 3 | /************************************************* |
| 4 | * Exim Monitor * |
| 5 | *************************************************/ |
| 6 | |
| 7 | /* Copyright (c) University of Cambridge 1995 - 2005 */ |
| 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 |
| 203 | host_extract_port(uschar *address) |
| 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 | |