1 /*************************************************
3 *************************************************/
5 /* Copyright (c) University of Cambridge 1995 - 2018 */
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, ...) PRINTF_FUNCTION(1,2);
61 static void show_log(char *s, ...)
66 uschar buffer[log_buffer_len + 24];
68 /* Do nothing if not tailing a log */
70 if (log_widget == NULL) return;
72 /* Initialize the text block structure */
78 /* We want to know whether the window has been scrolled back or not,
79 so that we can cease automatically scrolling with new text. This turns
80 out to be tricky with the text widget. We can detect whether the
81 scroll bar has been operated by checking on the "top" value, but it's
82 harder to detect that it has been returned to the bottom. The following
83 heuristic does its best. */
85 newtop = XawTextTopPosition(log_widget);
90 visible = size - top; /* save size of window */
91 scrolled = newtop < top;
93 else if (newtop > size - visible) scrolled = FALSE;
97 /* Format the text that is to be written. */
100 vsprintf(CS buffer, s, ap);
102 length = Ustrlen(buffer);
104 /* If we are anonymizing for screen shots, flatten various things. */
108 uschar *p = buffer + 9;
109 if (p[6] == '-' && p[13] == '-') p += 17;
111 while (p < buffer + length)
115 /* Check for strings to be left alone */
117 for (i = 0; i < oklist_size; i++)
119 int len = Ustrlen(oklist[i]);
120 if (Ustrncmp(p, oklist[i], len) == 0)
126 if (i < oklist_size) continue;
128 /* Leave driver names, size, protocol, alone */
130 if ((*p == 'D' || *p == 'P' || *p == 'T' || *p == 'S' || *p == 'R') &&
134 while (*p != ' ' && *p != 0) p++;
138 /* Leave C= text alone */
140 if (Ustrncmp(p, "C=\"", 3) == 0)
143 while (*p != 0 && *p != '"') p++;
147 /* Flatten remaining chars */
149 if (isdigit(*p)) *p++ = 'x';
150 else if (isalpha(*p)) *p++ = 'x';
156 /* If this would overflow the buffer, throw away 50% of the
157 current stuff in the buffer. Code defensively against odd
158 extreme cases that shouldn't actually arise. */
160 if (size + length > log_buffer_size)
162 if (size == 0) length = log_buffer_size/2; else
164 int cutcount = log_buffer_size/2;
165 if (cutcount > size) cutcount = size; else
167 while (cutcount < size && log_display_buffer[cutcount] != '\n')
172 XawTextReplace(log_widget, 0, cutcount, &b);
175 if (top < 0) top = 0;
176 if (top < cutcount) XawTextInvalidate(log_widget, 0, 999999);
177 xs_SetValues(log_widget, 1, "displayPosition", top);
181 /* Insert the new text at the end of the buffer. */
184 XawTextReplace(log_widget, 999999, 999999, &b);
187 /* When not scrolled back, we want to keep the bottom line
188 always visible. Put the insert point at the start of it because
189 this stops left/right scrolling with some X libraries. */
193 XawTextSetInsertionPoint(log_widget, size - length);
194 top = XawTextTopPosition(log_widget);
201 /*************************************************
202 * Function to read the log *
203 *************************************************/
205 /* We read any new log entries, and use their data to
206 updated total counts for the configured stripcharts.
207 The count for the queue chart is handled separately.
208 We also munge the log entries and display a one-line
209 version in the log window. */
213 struct stat statdata;
214 uschar buffer[log_buffer_len];
216 /* If log is not yet open, skip all of this. */
220 if (fseek(LOG, log_position, SEEK_SET))
222 perror("logfile fseek");
226 while (Ufgets(buffer, log_buffer_len, LOG) != NULL)
231 int length = Ustrlen(buffer);
232 pcre2_match_data * md = pcre2_match_data_create(1, NULL);
234 /* Skip totally blank lines (paranoia: there shouldn't be any) */
236 while (*p == ' ' || *p == '\t') p++;
237 if (*p == '\n') continue;
239 /* We should now have a complete log entry in the buffer; check
240 it for various regular expression matches and take appropriate
241 action. Get the current store point so we can reset to it. */
243 reset_point = store_mark();
245 /* First, update any stripchart data values, noting that the zeroth
246 stripchart is the queue length, which is handled elsewhere, and the
247 1st may the a size monitor. */
249 for (int i = stripchart_varstart; i < stripchart_number; i++)
250 if (pcre2_match(stripchart_regex[i], (PCRE2_SPTR)buffer, length,
251 0, PCRE_EOPT, md, NULL) >= 0)
252 stripchart_total[i]++;
254 /* Munge the log entry and display shortened form on one line.
255 We omit the date and show only the time. Remove any time zone offset.
256 Take note of the presence of [pid]. */
258 if (pcre2_match(yyyymmdd_regex, (PCRE2_SPTR) buffer, length, 0, PCRE_EOPT,
262 if ( (buffer[20] == '+' || buffer[20] == '-')
263 && isdigit(buffer[21]) && buffer[25] == ' ')
264 memmove(buffer + 20, buffer + 26, Ustrlen(buffer + 26) + 1);
265 if (buffer[20] == '[')
266 while (Ustrchr("[]0123456789", buffer[20+pidlength++]) != NULL)
268 id = string_copyn(buffer + 20 + pidlength, MESSAGE_ID_LENGTH);
269 show_log("%s", buffer+11);
274 show_log("%s", buffer);
276 pcre2_match_data_free(md);
278 /* Deal with frozen and unfrozen messages */
280 if (strstric(buffer, US"frozen", FALSE) != NULL)
282 queue_item *qq = find_queue(id, queue_noop, 0);
284 qq->frozen = strstric(buffer, US"unfrozen", FALSE) == NULL;
287 /* Notice defer messages, and add the destination if it
288 isn't already on the list for this message, with a pointer
289 to the parent if we can. */
291 if ((p = Ustrstr(buffer, "==")) != NULL)
293 queue_item *qq = find_queue(id, queue_noop, 0);
299 while (isspace(*p)) p++;
301 while (*p != 0 && !isspace(*p))
303 if (*p++ != '\"') continue;
306 if (*p == '\\') p += 2;
307 else if (*p++ == '\"') break;
311 if ((r = strstric(q, qualify_domain, FALSE)) != NULL &&
312 *(--r) == '@') *r = 0;
314 /* If we already have this destination, as tested case-insensitively,
315 do not add it to the destinations list. */
317 d = find_dest(qq, q, dest_add, TRUE);
319 if (d->parent == NULL)
321 while (isspace(*p)) p++;
326 while (*p != 0 && *p != '>') p++;
328 if ((p = strstric(q, qualify_domain, FALSE)) != NULL &&
329 *(--p) == '@') *p = 0;
330 dd = find_dest(qq, q, dest_noop, FALSE);
331 if (dd != NULL && dd != d) d->parent = dd;
337 store_reset(reset_point);
342 /* We have to detect when the log file is changed, and switch to the new file.
343 In practice, for non-datestamped files, this means that some deliveries might
344 go unrecorded, since they'll be written to the old file, but this usually
345 happens in the middle of the night, and I don't think the hassle of keeping
346 track of two log files is worth it.
348 First we check the datestamped name of the log file if necessary; if it is
349 different to the file we currently have open, go for the new file. As happens
350 in Exim itself, we leave in the following inode check, even when datestamping
351 because it does no harm and will cope should a file actually be renamed for
354 The test for a changed log file is to look up the inode of the file by name and
355 compare it with the saved inode of the file we currently are processing. This
356 accords with the usual interpretation of POSIX and other Unix specs that imply
357 "one file, one inode". However, it appears that on some Digital systems, if an
358 open file is unlinked, a new file may be created with the same inode while the
359 old file remains in existence. This can happen if the old log file is renamed,
360 processed in some way, and then deleted. To work round this, also test for a
361 link count of zero on the currently open file. */
363 if (log_datestamping)
365 uschar log_file_wanted[256];
366 /* Do *not* use "%s" here, we need the %D datestamp in the log_file string to
367 be expanded. The trailing NULL arg is to quieten preprocessors that need at
368 least one arg for a variadic set in a macro. */
369 string_format(log_file_wanted, sizeof(log_file_wanted), CS log_file, NULL);
370 if (Ustrcmp(log_file_wanted, log_file_open) != 0)
377 Ustrcpy(log_file_open, log_file_wanted);
382 (fstat(fileno(LOG), &statdata) == 0 && statdata.st_nlink == 0) ||
383 (Ustat(log_file, &statdata) == 0 && log_inode != statdata.st_ino))
387 /* Experiment shows that sometimes you can't immediately open
388 the new log file - presumably immediately after the old one
389 is renamed and before the new one exists. Therefore do a
390 trial open first to be sure. */
392 if ((TEST = fopen(CS log_file_open, "r")) != NULL)
394 if (LOG != NULL) fclose(LOG);
396 if (fstat(fileno(LOG), &statdata))
398 fprintf(stderr, "fstat %s: %s\n", log_file_open, strerror(errno));
401 log_inode = statdata.st_ino;
405 /* Save the position we have got to in the log. */
407 if (LOG != NULL) log_position = ftell(LOG);
410 /* End of em_log.c */