Linux bluetooth development
 help / color / mirror / Atom feed
From: Luiz Augusto von Dentz <luiz.dentz@gmail.com>
To: linux-bluetooth@vger.kernel.org
Subject: [PATCH BlueZ v1 2/6] monitor: Separate the frames for a pager reading records
Date: Thu, 17 Sep 2026 16:45:37 -0400	[thread overview]
Message-ID: <20260917204541.2176720-3-luiz.dentz@gmail.com> (raw)
In-Reply-To: <20260917204541.2176720-1-luiz.dentz@gmail.com>

From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>

A frame spans several lines, so a pager that works on records rather than
lines has no way of telling where one ends. Feeding a trace to fzf with
--read0 makes the whole of it a single entry, and without --read0 each
line becomes an entry of its own, so searching finds the line reporting
an error without the header saying which controller, handle and time it
belongs to.

Separate the frames with a NUL when the pager is going to read records,
which is asked for either by --print0 or by --read0 already being in
PAGER:

  $ PAGER='fzf --read0' btmon -r hcidump.log
  $ btmon -r hcidump.log --print0 | fzf --read0

Nothing is separated unless it was asked for, so the output is unchanged
for a pager reading lines, which would otherwise show the separator as a
stray character between the frames.

The model wrote this, which the author reviewed and verified by counting
the separators against the frames, by checking that a pager reading lines
still receives none, and that the output is unchanged when neither is
asked for.

Assisted-by: opencode:claude-opus-5
---
 Makefile.tools    |   1 +
 monitor/control.c |  15 ++++++
 monitor/find.c    | 128 ++++++++++++++++++++++++++++++++++++++++++++++
 monitor/find.h    |  19 +++++++
 monitor/main.c    |  15 +++++-
 5 files changed, 177 insertions(+), 1 deletion(-)
 create mode 100644 monitor/find.c
 create mode 100644 monitor/find.h

diff --git a/Makefile.tools b/Makefile.tools
index 6844a52ac739..671b40fb0e42 100644
--- a/Makefile.tools
+++ b/Makefile.tools
@@ -58,6 +58,7 @@ monitor_btmon_SOURCES = monitor/main.c monitor/bt.h \
 				monitor/jlink.h monitor/jlink.c \
 				monitor/tty.h monitor/emulator.h \
 				monitor/att.h monitor/att.c \
+				monitor/find.h monitor/find.c \
 				src/log.h src/log.c \
 				src/textfile.h src/textfile.c \
 				src/settings.h src/settings.c
diff --git a/monitor/control.c b/monitor/control.c
index 8ed221e08a02..4be6a4687005 100644
--- a/monitor/control.c
+++ b/monitor/control.c
@@ -39,6 +39,7 @@
 #include "src/shared/mainloop.h"
 
 #include "display.h"
+#include "find.h"
 #include "packet.h"
 #include "hcidump.h"
 #include "ellisys.h"
@@ -962,8 +963,10 @@ static void data_callback(int fd, uint32_t events, void *user_data)
 
 		switch (data->channel) {
 		case HCI_CHANNEL_CONTROL:
+			find_frame_begin();
 			packet_control(tv, cred, index, opcode,
 							data->buf, pktlen);
+			find_frame_end();
 			break;
 		case HCI_CHANNEL_MONITOR:
 			if (pktlen > (len - MGMT_HDR_SIZE))
@@ -973,8 +976,10 @@ static void data_callback(int fd, uint32_t events, void *user_data)
 							data->buf, pktlen);
 			ellisys_inject_hci(tv, index, opcode,
 							data->buf, pktlen);
+			find_frame_begin();
 			packet_monitor(tv, cred, index, opcode,
 							data->buf, pktlen);
+			find_frame_end();
 			break;
 		}
 	}
@@ -1120,8 +1125,10 @@ static void kmsg_callback(int fd, uint32_t events, void *user_data)
 
 	btsnoop_write_hci(btsnoop_file, &tv, HCI_DEV_NONE,
 				BTSNOOP_OPCODE_SYSTEM_NOTE, 0, msg, len);
+	find_frame_begin();
 	packet_monitor(&tv, NULL, HCI_DEV_NONE,
 				BTSNOOP_OPCODE_SYSTEM_NOTE, msg, len);
+	find_frame_end();
 }
 
 static int open_kmsg(void)
@@ -1168,8 +1175,10 @@ static void client_callback(int fd, uint32_t events, void *user_data)
 		opcode = le16_to_cpu(hdr->opcode);
 		index = le16_to_cpu(hdr->index);
 
+		find_frame_begin();
 		packet_monitor(NULL, NULL, index, opcode,
 					data->buf + MGMT_HDR_SIZE, pktlen);
+		find_frame_end();
 
 		data->offset -= pktlen + MGMT_HDR_SIZE;
 
@@ -1386,8 +1395,10 @@ static void process_data(struct control_data *data)
 					hdr->ext_hdr + hdr->hdr_len, pktlen);
 		ellisys_inject_hci(tv, 0, opcode, hdr->ext_hdr + hdr->hdr_len,
 					pktlen);
+		find_frame_begin();
 		packet_monitor(tv, NULL, 0, opcode,
 					hdr->ext_hdr + hdr->hdr_len, pktlen);
+		find_frame_end();
 
 		data->offset -= 2 + data_len;
 
@@ -1573,7 +1584,9 @@ void control_reader(const char *path, bool pager)
 			if (opcode == 0xffff)
 				continue;
 
+			find_frame_begin();
 			packet_monitor(&tv, NULL, index, opcode, buf, pktlen);
+			find_frame_end();
 			ellisys_inject_hci(&tv, index, opcode, buf, pktlen);
 		}
 		break;
@@ -1586,7 +1599,9 @@ void control_reader(const char *path, bool pager)
 								buf, &pktlen))
 				break;
 
+			find_frame_begin();
 			packet_simulator(&tv, frequency, buf, pktlen);
+			find_frame_end();
 		}
 		break;
 	}
diff --git a/monitor/find.c b/monitor/find.c
new file mode 100644
index 000000000000..649d722ee197
--- /dev/null
+++ b/monitor/find.c
@@ -0,0 +1,128 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2026  Intel Corporation
+ *
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "monitor/display.h"
+#include "monitor/find.h"
+
+static bool enabled;
+static bool records;	/* Separate the frames with a NUL */
+static bool printed;	/* Something has been written out already */
+static bool print0;
+
+static char *capture_buf;
+static size_t capture_len;
+static FILE *capture;
+
+void find_set_print0(void)
+{
+	print0 = true;
+}
+
+/*
+ * A frame spans several lines, so a pager that works on records rather
+ * than lines has to be told where one ends. The request is already in
+ * PAGER, so take it from there as well as from the command line.
+ */
+static bool want_records(void)
+{
+	const char *pager;
+
+	if (print0)
+		return true;
+
+	pager = getenv("PAGER");
+
+	return pager && strstr(pager, "--read0");
+}
+
+void find_setup(void)
+{
+	records = want_records();
+
+	/*
+	 * Nothing is kept, but the output still has to be seen to know
+	 * where one frame ends and the next begins.
+	 */
+	if (records)
+		enabled = true;
+}
+
+void find_frame_begin(void)
+{
+	if (!enabled)
+		return;
+
+	capture_buf = NULL;
+	capture_len = 0;
+
+	capture = open_memstream(&capture_buf, &capture_len);
+	if (!capture)
+		return;
+
+	display_set_output(capture);
+}
+
+void find_frame_end(void)
+{
+	if (!enabled || !capture)
+		return;
+
+	display_set_output(NULL);
+	fclose(capture);
+	capture = NULL;
+
+	/* A frame that was filtered out produces no output */
+	if (!capture_len) {
+		free(capture_buf);
+		capture_buf = NULL;
+		return;
+	}
+
+	if (records) {
+		const char nul = '\0';
+		size_t len = capture_len;
+
+		/*
+		 * The newline ending the frame is what separates the lines
+		 * within it, so leave it out of a record or it shows as a
+		 * blank line at the end of the entry.
+		 */
+		if (len && capture_buf[len - 1] == '\n')
+			len--;
+
+		if (printed)
+			fwrite(&nul, 1, 1, stdout);
+
+		fwrite(capture_buf, 1, len, stdout);
+	} else {
+		fwrite(capture_buf, 1, capture_len, stdout);
+	}
+
+	fflush(stdout);
+	printed = true;
+
+	free(capture_buf);
+	capture_buf = NULL;
+}
+
+void find_cleanup(void)
+{
+	enabled = false;
+}
diff --git a/monitor/find.h b/monitor/find.h
new file mode 100644
index 000000000000..c6b2036ba3a6
--- /dev/null
+++ b/monitor/find.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2026  Intel Corporation
+ *
+ *
+ */
+
+#include <stdbool.h>
+
+void find_set_print0(void);
+void find_setup(void);
+
+void find_frame_begin(void);
+void find_frame_end(void);
+
+void find_cleanup(void);
diff --git a/monitor/main.c b/monitor/main.c
index dc37294c5c27..ce7bae7f5cae 100644
--- a/monitor/main.c
+++ b/monitor/main.c
@@ -31,6 +31,7 @@
 #include "ellisys.h"
 #include "control.h"
 #include "display.h"
+#include "find.h"
 
 static void signal_callback(int signum, void *user_data)
 {
@@ -69,6 +70,8 @@ static void usage(void)
 		"\t-I, --iso              Dump ISO traffic\n"
 		"\t-E, --ellisys [ip]     Send Ellisys HCI Injection\n"
 		"\t-P, --no-pager         Disable pager usage\n"
+		"\t-0, --print0           Separate frames with a NUL, for a\n"
+		"\t                       pager reading records\n"
 		"\t-J  --jlink <device>,[<serialno>],[<interface>],[<speed>]\n"
 		"\t                       Read data from RTT\n"
 		"\t-R  --rtt [<address>],[<area>],[<name>]\n"
@@ -98,6 +101,7 @@ static const struct option main_options[] = {
 	{ "iso",       no_argument,       NULL, 'I' },
 	{ "ellisys",   required_argument, NULL, 'E' },
 	{ "no-pager",  no_argument,       NULL, 'P' },
+	{ "print0",    no_argument,       NULL, '0' },
 	{ "jlink",     required_argument, NULL, 'J' },
 	{ "rtt",       required_argument, NULL, 'R' },
 	{ "columns",   required_argument, NULL, 'C' },
@@ -133,7 +137,7 @@ int main(int argc, char *argv[])
 		struct sockaddr_un addr;
 
 		opt = getopt_long(argc, argv,
-				"r:w:a:s:p:i:d:B:V:MKNtTSAIE:PJ:R:C:c:vh",
+				"r:w:a:s:p:i:d:B:V:MKNtTSAIE:P0J:R:C:c:vh",
 				main_options, NULL);
 		if (opt < 0)
 			break;
@@ -217,6 +221,9 @@ int main(int argc, char *argv[])
 		case 'P':
 			use_pager = false;
 			break;
+		case '0':
+			find_set_print0();
+			break;
 		case 'J':
 			jlink = optarg;
 			break;
@@ -279,6 +286,8 @@ int main(int argc, char *argv[])
 		if (ellisys_server)
 			ellisys_enable(ellisys_server, ellisys_port);
 
+		find_setup();
+
 		control_reader(reader_path, use_pager);
 		return EXIT_SUCCESS;
 	}
@@ -300,8 +309,12 @@ int main(int argc, char *argv[])
 	if (jlink && control_rtt(jlink, rtt) < 0)
 		return EXIT_FAILURE;
 
+	find_setup();
+
 	exit_status = mainloop_run_with_signal(signal_callback, NULL);
 
+	find_cleanup();
+
 	keys_cleanup();
 
 	return exit_status;
-- 
2.55.0


  parent reply	other threads:[~2026-09-17 20:45 UTC|newest]

Thread overview: 8+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-17 20:45 [PATCH BlueZ v1 0/6] monitor: Search the frames with a pager Luiz Augusto von Dentz
2026-09-17 20:45 ` [PATCH BlueZ v1 1/6] monitor: Route the decoding output through a single function Luiz Augusto von Dentz
2026-09-17 23:55   ` monitor: Search the frames with a pager bluez.test.bot
2026-09-17 20:45 ` Luiz Augusto von Dentz [this message]
2026-09-17 20:45 ` [PATCH BlueZ v1 3/6] monitor: Hand the captured frames to the pager on Ctrl-R Luiz Augusto von Dentz
2026-09-17 20:45 ` [PATCH BlueZ v1 4/6] doc/btmon: Document searching the frames Luiz Augusto von Dentz
2026-09-17 20:45 ` [PATCH BlueZ v1 5/6] monitor: Prefer fzf as the pager when one is not set Luiz Augusto von Dentz
2026-09-17 20:45 ` [PATCH BlueZ v1 6/6] doc/btmon: Document the pager choice Luiz Augusto von Dentz

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260917204541.2176720-3-luiz.dentz@gmail.com \
    --to=luiz.dentz@gmail.com \
    --cc=linux-bluetooth@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox