summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJeremy Harris <jgh146exb@wizmail.org>2022-02-04 21:53:28 +0000
committerJeremy Harris <jgh146exb@wizmail.org>2022-02-05 19:34:43 +0000
commit19fdbfb4a2b6ca4a6a96ef52be848f0a23e2414f (patch)
tree0e11463bafd367fc15eb5451d9ba953821431650 /src
parentacfde2172e82f6b776f16d78f789c6968b69b9f0 (diff)
Debug pretrigger capture facility
Diffstat (limited to 'src')
-rw-r--r--src/src/acl.c34
-rw-r--r--src/src/child.c24
-rw-r--r--src/src/debug.c88
-rw-r--r--src/src/exim.c27
-rw-r--r--src/src/functions.h3
-rw-r--r--src/src/globals.c5
-rw-r--r--src/src/globals.h4
-rw-r--r--src/src/log.c15
-rw-r--r--src/src/macros.h9
9 files changed, 174 insertions, 35 deletions
diff --git a/src/src/acl.c b/src/src/acl.c
index 19c1bbbd9..fa1172331 100644
--- a/src/src/acl.c
+++ b/src/src/acl.c
@@ -3498,25 +3498,39 @@ for (; cb; cb = cb->next)
}
else if (Ustrncmp(pp, "kill", 4) == 0)
{
- for (pp += 4; *pp && *pp != '/';) pp++;
+ pp += 4;
kill = TRUE;
}
else if (Ustrncmp(pp, "stop", 4) == 0)
{
- for (pp += 4; *pp && *pp != '/';) pp++;
+ pp += 4;
stop = TRUE;
}
- else
- while (*pp && *pp != '/') pp++;
+ else if (Ustrncmp(pp, "pretrigger=", 11) == 0)
+ debug_pretrigger_setup(pp+11);
+ else if (Ustrncmp(pp, "trigger=", 8) == 0)
+ {
+ if (Ustrncmp(pp += 8, "now", 3) == 0)
+ {
+ pp += 3;
+ debug_trigger_fire();
+ }
+ else if (Ustrncmp(pp, "paniclog", 8) == 0)
+ {
+ pp += 8;
+ dtrigger_selector |= BIT(DTi_panictrigger);
+ }
+ }
+ while (*pp && *pp != '/') pp++;
p = pp;
}
- if (kill)
- debug_logging_stop(TRUE);
- else if (stop)
- debug_logging_stop(FALSE);
- else
- debug_logging_activate(debug_tag, debug_opts);
+ if (kill)
+ debug_logging_stop(TRUE);
+ else if (stop)
+ debug_logging_stop(FALSE);
+ else if (debug_tag || debug_opts)
+ debug_logging_activate(debug_tag, debug_opts);
break;
}
diff --git a/src/src/child.c b/src/src/child.c
index 4a262d623..07115bee4 100644
--- a/src/src/child.c
+++ b/src/src/child.c
@@ -76,7 +76,7 @@ int n = 0;
int extra = pcount ? *pcount : 0;
uschar **argv;
-argv = store_get((extra + acount + MAX_CLMACROS + 21) * sizeof(char *), FALSE);
+argv = store_get((extra + acount + MAX_CLMACROS + 24) * sizeof(char *), FALSE);
/* In all case, the list starts out with the path, any macros, and a changed
config file. */
@@ -88,10 +88,7 @@ if (clmacro_count > 0)
n += clmacro_count;
}
if (f.config_changed)
- {
- argv[n++] = US"-C";
- argv[n++] = config_main_filename;
- }
+ { argv[n++] = US"-C"; argv[n++] = config_main_filename; }
/* These values are added only for non-minimal cases. If debug_selector is
precisely D_v, we have to assume this was started by a non-admin user, and
@@ -120,22 +117,23 @@ if (!minimal)
}
}
}
+ if (debug_pretrigger_buf)
+ { argv[n++] = US"-dp"; argv[n++] = string_sprintf("0x%x", debug_pretrigger_bsize); }
+ if (dtrigger_selector != 0)
+ argv[n++] = string_sprintf("-dt=0x%x", dtrigger_selector);
DEBUG(D_any)
{
argv[n++] = US"-MCd";
argv[n++] = US process_purpose;
}
- if (!f.testsuite_delays) argv[n++] = US"-odd";
- if (f.dont_deliver) argv[n++] = US"-N";
- if (f.queue_smtp) argv[n++] = US"-odqs";
- if (f.synchronous_delivery) argv[n++] = US"-odi";
+ if (!f.testsuite_delays) argv[n++] = US"-odd";
+ if (f.dont_deliver) argv[n++] = US"-N";
+ if (f.queue_smtp) argv[n++] = US"-odqs";
+ if (f.synchronous_delivery) argv[n++] = US"-odi";
if (connection_max_messages >= 0)
argv[n++] = string_sprintf("-oB%d", connection_max_messages);
if (*queue_name)
- {
- argv[n++] = US"-MCG";
- argv[n++] = queue_name;
- }
+ { argv[n++] = US"-MCG"; argv[n++] = queue_name; }
}
/* Now add in any others that are in the call. Remember which they were,
diff --git a/src/src/debug.c b/src/src/debug.c
index 92cad6d48..7b9be057b 100644
--- a/src/src/debug.c
+++ b/src/src/debug.c
@@ -13,6 +13,8 @@ static uschar debug_buffer[2048];
static uschar *debug_ptr = debug_buffer;
static int debug_prefix_length = 0;
+static unsigned pretrigger_writeoff;
+static unsigned pretrigger_readoff;
const uschar * rc_names[] = { /* Mostly for debug output */
@@ -318,8 +320,40 @@ if (debug_ptr[-1] == '\n')
}
}
- fprintf(debug_file, "%s", CS debug_buffer);
- fflush(debug_file);
+ if (debug_pretrigger_buf)
+ {
+ int needed = Ustrlen(debug_buffer), avail;
+ char c;
+
+ if (needed > debug_pretrigger_bsize)
+ needed = debug_pretrigger_bsize;
+ if ((avail = pretrigger_readoff - pretrigger_writeoff) <= 0)
+ avail += debug_pretrigger_bsize;
+
+ /* We have a pretrigger set up, trigger not yet hit. Copy the line(s) to the
+ pretrig buffer, dropping earlier lines if needed but truncating this line if
+ the pbuf is maxed out. In the PTB the lines are NOT nul-terminated. */
+
+ while (avail < needed)
+ do
+ {
+ avail++;
+ c = debug_pretrigger_buf[pretrigger_readoff];
+ if (++pretrigger_readoff >= debug_pretrigger_bsize) pretrigger_readoff = 0;
+ }
+ while (c && c != '\n' && pretrigger_readoff != pretrigger_writeoff);
+
+ for (int i = 0; needed; i++, needed--)
+ {
+ debug_pretrigger_buf[pretrigger_writeoff] = debug_buffer[i];
+ if (++pretrigger_writeoff >= debug_pretrigger_bsize) pretrigger_writeoff = 0;
+ }
+ }
+ else
+ {
+ fprintf(debug_file, "%s", CS debug_buffer);
+ fflush(debug_file);
+ }
debug_ptr = debug_buffer;
debug_prefix_length = 0;
}
@@ -410,4 +444,54 @@ else
}
+/**************************************************************/
+/* Pretrigger handling for debug. The debug_printf implementation
+diverts output to a circular buffer if the buffer is set up.
+The routines here set up the buffer, and unload it to file (and release it).
+What ends up in the buffer is subject to the usual debug_selector. */
+
+void
+debug_pretrigger_setup(const uschar * size_string)
+{
+long size = Ustrtol(size_string, NULL, 0);
+if (size > 0)
+ {
+ unsigned bufsize = MIN(size, 16384);
+
+ dtrigger_selector |= BIT(DTi_pretrigger);
+ if (debug_pretrigger_buf) store_free(debug_pretrigger_buf);
+ debug_pretrigger_buf = store_malloc((size_t)(debug_pretrigger_bsize = bufsize));
+ pretrigger_readoff = pretrigger_writeoff = 0;
+ }
+}
+
+void
+debug_trigger_fire(void)
+{
+int nbytes;
+
+if (!debug_pretrigger_buf) return;
+
+if (debug_file && (nbytes = pretrigger_writeoff - pretrigger_readoff) != 0)
+ if (nbytes > 0)
+ fwrite(debug_pretrigger_buf + pretrigger_readoff, 1, nbytes, debug_file);
+ else
+ {
+ fwrite(debug_pretrigger_buf + pretrigger_readoff, 1,
+ debug_pretrigger_bsize - pretrigger_readoff, debug_file);
+ fwrite(debug_pretrigger_buf, 1, pretrigger_writeoff, debug_file);
+ }
+
+debug_pretrigger_discard();
+}
+
+void
+debug_pretrigger_discard(void)
+{
+if (debug_pretrigger_buf) store_free(debug_pretrigger_buf);
+debug_pretrigger_buf = NULL;
+dtrigger_selector = 0;
+}
+
+
/* End of debug.c */
diff --git a/src/src/exim.c b/src/src/exim.c
index 664d5d7f1..b59157286 100644
--- a/src/src/exim.c
+++ b/src/src/exim.c
@@ -2655,21 +2655,36 @@ on the second character (the one after '-'), to save some effort. */
#endif
break;
- /* -d: Set debug level (see also -v below) or set the drop_cr option.
- The latter is now a no-op, retained for compatibility only. If -dd is used,
- debugging subprocesses of the daemon is disabled. */
-
case 'd':
+
+ /* -dropcr: Set this option. Now a no-op, retained for compatibility only. */
+
if (Ustrcmp(argrest, "ropcr") == 0)
{
/* drop_cr = TRUE; */
}
- /* Use an intermediate variable so that we don't set debugging while
- decoding the debugging bits. */
+ /* -dp: Set up a debug pretrigger buffer with given size. */
+
+ else if (Ustrcmp(argrest, "p") == 0)
+ if (++i >= argc)
+ badarg = TRUE;
+ else
+ debug_pretrigger_setup(argv[i]);
+
+ /* -dt: Set a debug trigger selector */
+
+ else if (Ustrncmp(argrest, "t=", 2) == 0)
+ dtrigger_selector = (unsigned int) Ustrtol(argrest + 2, NULL, 0);
+
+ /* -d: Set debug level (see also -v below).
+ If -dd is used, debugging subprocesses of the daemon is disabled. */
else
{
+ /* Use an intermediate variable so that we don't set debugging while
+ decoding the debugging bits. */
+
unsigned int selector = D_default;
debug_selector = 0;
debug_file = NULL;
diff --git a/src/src/functions.h b/src/src/functions.h
index d27c23baa..4b4ff5e14 100644
--- a/src/src/functions.h
+++ b/src/src/functions.h
@@ -193,7 +193,10 @@ extern void debug_printf_indent(const char *, ...) PRINTF_FUNCTION(1,2);
extern void debug_print_string(uschar *);
extern void debug_print_tree(const char *, tree_node *);
extern void debug_vprintf(int, const char *, va_list);
+extern void debug_pretrigger_setup(const uschar *);
+extern void debug_pretrigger_discard(void);
extern void debug_print_socket(int);
+extern void debug_trigger_fire(void);
extern void decode_bits(unsigned int *, size_t, int *,
uschar *, bit_table *, int, uschar *, int);
diff --git a/src/src/globals.c b/src/src/globals.c
index 7f7ceac00..7e50867b2 100644
--- a/src/src/globals.c
+++ b/src/src/globals.c
@@ -815,8 +815,10 @@ bit_table debug_options[] = { /* must be in alphabetical order and use
BIT_TABLE(D, verify),
};
int debug_options_count = nelem(debug_options);
-
+unsigned debug_pretrigger_bsize= 0;
+uschar * debug_pretrigger_buf = NULL;
unsigned int debug_selector = 0;
+
int delay_warning[DELAY_WARNING_SIZE] = { DELAY_WARNING_SIZE, 1, 24*60*60 };
uschar *delay_warning_condition=
US"${if or {"
@@ -897,6 +899,7 @@ uschar *dnslist_value = NULL;
tree_node *domainlist_anchor = NULL;
int domainlist_count = 0;
uschar *dsn_from = US DEFAULT_DSN_FROM;
+unsigned int dtrigger_selector = 0;
int errno_quota = ERRNO_QUOTA;
uschar *errors_copy = NULL;
diff --git a/src/src/globals.h b/src/src/globals.h
index 10dd3effc..de4670a81 100644
--- a/src/src/globals.h
+++ b/src/src/globals.h
@@ -487,6 +487,8 @@ extern FILE *debug_file; /* Where to write debugging info */
extern int debug_notall[]; /* Debug options excluded from +all */
extern bit_table debug_options[]; /* Table of debug options */
extern int debug_options_count; /* Size of table */
+extern unsigned debug_pretrigger_bsize;
+extern uschar *debug_pretrigger_buf; /* circular buffer for precapture */
extern BOOL debug_store; /* Do extra checks on store_reset */
extern int delay_warning[]; /* Times between warnings */
extern uschar *delay_warning_condition; /* Condition string for warnings */
@@ -576,6 +578,8 @@ extern int domainlist_count; /* Number defined */
/* This option is now a no-opt, retained for compatibility */
extern BOOL drop_cr; /* For broken local MUAs */
+extern unsigned int dtrigger_selector; /* when to start debug */
+
extern uschar *dsn_from; /* From: string for DSNs */
extern BOOL envelope_to_remove; /* Remove envelope_to_headers */
diff --git a/src/src/log.c b/src/src/log.c
index fe9bd0c9f..8bdb244bd 100644
--- a/src/src/log.c
+++ b/src/src/log.c
@@ -905,6 +905,11 @@ if (!path_inspected)
"More than one path given in log_file_path: using %s", file_path);
}
+/* Optionally trigger debug */
+
+if (flags & LOG_PANIC && dtrigger_selector & BIT(DTi_panictrigger))
+ debug_trigger_fire();
+
/* If debugging, show all log entries, but don't show headers. Do it all
in one go so that it doesn't get split when multi-processing. */
@@ -1470,7 +1475,11 @@ misconfiguration.
The first use of this is in ACL logic, "control = debug/tag=foo/opts=+expand"
which can be combined with conditions, etc, to activate extra logging only
-for certain sources. The second use is inetd wait mode debug preservation. */
+for certain sources. The second use is inetd wait mode debug preservation.
+
+It might be nice, in ACL-initiated pretrigger mode, to not create the file
+immediately but only upon a trigger - but we'd need another cmdline option
+to pass the name through child_exxec_exim(). */
void
debug_logging_activate(uschar *tag_name, uschar *opts)
@@ -1482,7 +1491,7 @@ if (debug_file)
return;
}
-if (tag_name != NULL && (Ustrchr(tag_name, '/') != NULL))
+if (tag_name && (Ustrchr(tag_name, '/') != NULL))
{
log_write(0, LOG_MAIN|LOG_PANIC, "debug tag may not contain a '/' in: %s",
tag_name);
@@ -1512,11 +1521,13 @@ else
void
debug_logging_stop(BOOL kill)
{
+debug_pretrigger_discard();
if (!debug_file || !debuglog_name[0]) return;
debug_selector = 0;
fclose(debug_file);
debug_file = NULL;
+debug_fd = -1;
if (kill) unlink_log(lt_debug);
}
diff --git a/src/src/macros.h b/src/src/macros.h
index 80e0ecbe1..5e26a7bce 100644
--- a/src/src/macros.h
+++ b/src/src/macros.h
@@ -106,7 +106,7 @@ don't make the file descriptors two-way. */
/* Debugging control */
#define DEBUG(x) if (debug_selector & (x))
-#define HDEBUG(x) if (host_checking || (debug_selector & (x)))
+#define HDEBUG(x) if (host_checking || debug_selector & (x))
/* The default From: text for DSNs */
@@ -422,6 +422,13 @@ enum {
D_timestamp | \
D_resolver))
+/* Bits for debug triggers */
+
+enum {
+ DTi_panictrigger,
+ DTi_pretrigger,
+};
+
/* Options bits for logging. Those that have values < BITWORDSIZE can be used
in calls to log_write(). The others are put into later words in log_selector
and are only ever tested independently, so they do not need bit mask