1 /*************************************************
3 *************************************************/
5 /* Copyright (c) University of Cambridge 1995 - 2009 */
6 /* See the file NOTICE for conditions of use and distribution. */
8 /* This module contains code for scanning the main log,
9 extracting information from it, and displaying a "tail". */
13 #define log_buffer_len 4096 /* For each log entry */
15 /* If anonymizing, don't alter these strings (this is all an ad hoc hack). */
18 static char *oklist[] = {
22 "Connection timed out",
23 "Start queue run: pid=",
24 "End queue run: pid=",
25 "host lookup did not complete",
26 "unexpected disconnection while reading SMTP command from",
27 "verify failed for SMTP recipient",
45 static int oklist_size = sizeof(oklist) / sizeof(uschar *);
50 /*************************************************
51 * Write to the log display *
52 *************************************************/
54 static int visible = 0;
55 static int scrolled = FALSE;
59 static void show_log(char *s, ...)
64 uschar buffer[log_buffer_len + 24];
66 /* Do nothing if not tailing a log */
68 if (log_widget == NULL) return;
70 /* Initialize the text block structure */
76 /* We want to know whether the window has been scrolled back or not,
77 so that we can cease automatically scrolling with new text. This turns
78 out to be tricky with the text widget. We can detect whether the
79 scroll bar has been operated by checking on the "top" value, but it's
80 harder to detect that it has been returned to the bottom. The following
81 heuristic does its best. */
83 newtop = XawTextTopPosition(log_widget);
88 visible = size - top; /* save size of window */
89 scrolled = newtop < top;
91 else if (newtop > size - visible) scrolled = FALSE;
95 /* Format the text that is to be written. */
98 vsprintf(CS buffer, s, ap);
100 length = Ustrlen(buffer);
102 /* If we are anonymizing for screen shots, flatten various things. */
106 uschar *p = buffer + 9;
107 if (p[6] == '-' && p[13] == '-') p += 17;
109 while (p < buffer + length)
113 /* Check for strings to be left alone */
115 for (i = 0; i < oklist_size; i++)
117 int len = Ustrlen(oklist[i]);
118 if (Ustrncmp(p, oklist[i], len) == 0)
124 if (i < oklist_size) continue;
126 /* Leave driver names, size, protocol, alone */
128 if ((*p == 'D' || *p == 'P' || *p == 'T' || *p == 'S' || *p == 'R') &&
132 while (*p != ' ' && *p != 0) p++;
136 /* Leave C= text alone */
138 if (Ustrncmp(p, "C=\"", 3) == 0)
141 while (*p != 0 && *p != '"') p++;
145 /* Flatten remaining chars */
147 if (isdigit(*p)) *p++ = 'x';
148 else if (isalpha(*p)) *p++ = 'x';
154 /* If this would overflow the buffer, throw away 50% of the
155 current stuff in the buffer. Code defensively against odd
156 extreme cases that shouldn't actually arise. */
158 if (size + length > log_buffer_size)
160 if (size == 0) length = log_buffer_size/2; else
162 int cutcount = log_buffer_size/2;
163 if (cutcount > size) cutcount = size; else
165 while (cutcount < size && log_display_buffer[cutcount] != '\n')
170 XawTextReplace(log_widget, 0, cutcount, &b);
173 if (top < 0) top = 0;
174 if (top < cutcount) XawTextInvalidate(log_widget, 0, 999999);
175 xs_SetValues(log_widget, 1, "displayPosition", top);
179 /* Insert the new text at the end of the buffer. */
182 XawTextReplace(log_widget, 999999, 999999, &b);
185 /* When not scrolled back, we want to keep the bottom line
186 always visible. Put the insert point at the start of it because
187 this stops left/right scrolling with some X libraries. */
191 XawTextSetInsertionPoint(log_widget, size - length);
192 top = XawTextTopPosition(log_widget);
199 /*************************************************
200 * Function to read the log *
201 *************************************************/
203 /* We read any new log entries, and use their data to
204 updated total counts for the configured stripcharts.
205 The count for the queue chart is handled separately.
206 We also munge the log entries and display a one-line
207 version in the log window. */
211 struct stat statdata;
212 uschar buffer[log_buffer_len];
214 /* If log is not yet open, skip all of this. */
218 fseek(LOG, log_position, SEEK_SET);
220 while (Ufgets(buffer, log_buffer_len, LOG) != NULL)
225 int length = Ustrlen(buffer);
228 /* Skip totally blank lines (paranoia: there shouldn't be any) */
230 while (*p == ' ' || *p == '\t') p++;
231 if (*p == '\n') continue;
233 /* We should now have a complete log entry in the buffer; check
234 it for various regular expression matches and take appropriate
235 action. Get the current store point so we can reset to it. */
237 reset_point = store_get(0);
239 /* First, update any stripchart data values, noting that the zeroth
240 stripchart is the queue length, which is handled elsewhere, and the
241 1st may the a size monitor. */
243 for (i = stripchart_varstart; i < stripchart_number; i++)
245 if (pcre_exec(stripchart_regex[i], NULL, CS buffer, length, 0, PCRE_EOPT,
247 stripchart_total[i]++;
250 /* Munge the log entry and display shortened form on one line.
251 We omit the date and show only the time. Remove any time zone offset.
252 Take note of the presence of [pid]. */
254 if (pcre_exec(yyyymmdd_regex,NULL,CS buffer,length,0,PCRE_EOPT,NULL,0) >= 0)
257 if ((buffer[20] == '+' || buffer[20] == '-') &&
258 isdigit(buffer[21]) && buffer[25] == ' ')
259 memmove(buffer + 20, buffer + 26, Ustrlen(buffer + 26) + 1);
260 if (buffer[20] == '[')
262 while (Ustrchr("[]0123456789", buffer[20+pidlength++]) != NULL);
264 id = string_copyn(buffer + 20 + pidlength, MESSAGE_ID_LENGTH);
265 show_log("%s", buffer+11);
270 show_log("%s", buffer);
273 /* Deal with frozen and unfrozen messages */
275 if (strstric(buffer, US"frozen", FALSE) != NULL)
277 queue_item *qq = find_queue(id, queue_noop, 0);
280 if (strstric(buffer, US"unfrozen", FALSE) != NULL)
282 else qq->frozen = TRUE;
286 /* Notice defer messages, and add the destination if it
287 isn't already on the list for this message, with a pointer
288 to the parent if we can. */
290 if ((p = Ustrstr(buffer, "==")) != NULL)
292 queue_item *qq = find_queue(id, queue_noop, 0);
298 while (isspace(*p)) p++;
300 while (*p != 0 && !isspace(*p))
302 if (*p++ != '\"') continue;
305 if (*p == '\\') p += 2;
306 else if (*p++ == '\"') break;
310 if ((r = strstric(q, qualify_domain, FALSE)) != NULL &&
311 *(--r) == '@') *r = 0;
313 /* If we already have this destination, as tested case-insensitively,
314 do not add it to the destinations list. */
316 d = find_dest(qq, q, dest_add, TRUE);
318 if (d->parent == NULL)
320 while (isspace(*p)) p++;
325 while (*p != 0 && *p != '>') p++;
327 if ((p = strstric(q, qualify_domain, FALSE)) != NULL &&
328 *(--p) == '@') *p = 0;
329 dd = find_dest(qq, q, dest_noop, FALSE);
330 if (dd != NULL && dd != d) d->parent = dd;
336 store_reset(reset_point);
341 /* We have to detect when the log file is changed, and switch to the new file.
342 In practice, for non-datestamped files, this means that some deliveries might
343 go unrecorded, since they'll be written to the old file, but this usually
344 happens in the middle of the night, and I don't think the hassle of keeping
345 track of two log files is worth it.
347 First we check the datestamped name of the log file if necessary; if it is
348 different to the file we currently have open, go for the new file. As happens
349 in Exim itself, we leave in the following inode check, even when datestamping
350 because it does no harm and will cope should a file actually be renamed for
353 The test for a changed log file is to look up the inode of the file by name and
354 compare it with the saved inode of the file we currently are processing. This
355 accords with the usual interpretation of POSIX and other Unix specs that imply
356 "one file, one inode". However, it appears that on some Digital systems, if an
357 open file is unlinked, a new file may be created with the same inode while the
358 old file remains in existence. This can happen if the old log file is renamed,
359 processed in some way, and then deleted. To work round this, also test for a
360 link count of zero on the currently open file. */
362 if (log_datestamping)
364 uschar log_file_wanted[256];
365 string_format(log_file_wanted, sizeof(log_file_wanted), CS log_file);
366 if (Ustrcmp(log_file_wanted, log_file_open) != 0)
373 Ustrcpy(log_file_open, log_file_wanted);
378 (fstat(fileno(LOG), &statdata) == 0 && statdata.st_nlink == 0) ||
379 (Ustat(log_file, &statdata) == 0 && log_inode != statdata.st_ino))
383 /* Experiment shows that sometimes you can't immediately open
384 the new log file - presumably immediately after the old one
385 is renamed and before the new one exists. Therefore do a
386 trial open first to be sure. */
388 if ((TEST = fopen(CS log_file_open, "r")) != NULL)
390 if (LOG != NULL) fclose(LOG);
392 fstat(fileno(LOG), &statdata);
393 log_inode = statdata.st_ino;
397 /* Save the position we have got to in the log. */
399 if (LOG != NULL) log_position = ftell(LOG);
402 /* End of em_log.c */