Linux Kernel Selftest development
 help / color / mirror / Atom feed
* [PATCH bpf-next v2 0/2] bpftool: Fail map dump for maps that can't be iterated
@ 2026-09-25 16:20 Maxim Skokov
  2026-09-25 16:20 ` [PATCH bpf-next v2 1/2] " Maxim Skokov
  2026-09-25 16:20 ` [PATCH bpf-next v2 2/2] selftests/bpf: Check that bpftool map dump fails for socket storage Maxim Skokov
  0 siblings, 2 replies; 5+ messages in thread
From: Maxim Skokov @ 2026-09-25 16:20 UTC (permalink / raw)
  To: bpf
  Cc: qmo, ast, daniel, andrii, eddyz87, memxor, martin.lau, song,
	yonghong.song, jolsa, emil, ihor.solodrai, kuba, shuah,
	linux-kselftest, Maxim Skokov

"bpftool map dump" treats only ENOENT from bpf_map_get_next_key() as the
end of the map. Map types that can't be iterated (local storage, ringbuf,
bloom filter, arena, queue, stack) fail on the first key, and bpftool
prints an empty dump, "[]" or "Found 0 elements", with only a non-zero
exit status.

Patch 1 asks for the first key before printing anything and fails with
the map type and the error. Patch 2 adds a test_progs case on a socket
storage map.

The new test fails without patch 1 and passes with it; the other bpftool_*
selftests pass. Tested on x86_64 under a bpf-next kernel with KASAN and
lockdep, by hand on queue, stack, bloom filter, ringbuf, arena, sk_storage
and empty and filled hash maps, and through BPF CI (x86_64, aarch64,
s390x) with only comment style differing from this posting.

Changes in v2:
- probe the first key before any output instead of printing a hint for
  ENOTSUPP only, so every non-iterable type gets an error and stdout gets
  no "[]" (Alexei)
- drop the Fixes tag and use a neutral example map (Alexei)
- selftests: check that JSON output is only the error object and plain
  stdout has no elements or count; clear the output buffer before each
  run (Sashiko)
- multi-line comment style (bpf-ci)

v1: https://lore.kernel.org/bpf/20260924160732.485650-1-skokovmaksimevg@gmail.com/

Maxim Skokov (2):
  bpftool: Fail map dump for maps that can't be iterated
  selftests/bpf: Check that bpftool map dump fails for socket storage

 tools/bpf/bpftool/map.c                       | 26 +++++-
 .../bpf/prog_tests/bpftool_map_dump.c         | 88 +++++++++++++++++++
 2 files changed, 112 insertions(+), 2 deletions(-)
 create mode 100644 tools/testing/selftests/bpf/prog_tests/bpftool_map_dump.c


base-commit: 4f3a5eae895b9995e93425a75235d8f1f3268caa
-- 
2.47.3


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH bpf-next v2 1/2] bpftool: Fail map dump for maps that can't be iterated
  2026-09-25 16:20 [PATCH bpf-next v2 0/2] bpftool: Fail map dump for maps that can't be iterated Maxim Skokov
@ 2026-09-25 16:20 ` Maxim Skokov
  2026-09-25 19:24   ` Quentin Monnet
  2026-09-25 16:20 ` [PATCH bpf-next v2 2/2] selftests/bpf: Check that bpftool map dump fails for socket storage Maxim Skokov
  1 sibling, 1 reply; 5+ messages in thread
From: Maxim Skokov @ 2026-09-25 16:20 UTC (permalink / raw)
  To: bpf
  Cc: qmo, ast, daniel, andrii, eddyz87, memxor, martin.lau, song,
	yonghong.song, jolsa, emil, ihor.solodrai, kuba, shuah,
	linux-kselftest, Maxim Skokov

"bpftool map dump" walks the map with bpf_map_get_next_key() and treats
ENOENT as the end of the map. Map types that can't be iterated fail that
call on the very first key: local storage and ringbuf with ENOTSUPP,
bloom filter and arena with EOPNOTSUPP, queue and stack with EINVAL.
bpftool then ends the walk without a word and prints what it prints for
an empty map, "[]" with BTF or "Found 0 elements" without, and only the
exit status tells that the map was not read. On a socket storage map
that holds storage for a socket:

  # bpftool map dump pinned /sys/fs/bpf/sk_storage
  []

Ask for the first key before printing anything, and when that fails with
anything but ENOENT, fail with the map type and the error instead of a
dump:

  # bpftool map dump pinned /sys/fs/bpf/sk_storage
  Error: can't dump sk_storage map: Unknown error 524
  # bpftool -j map dump pinned /sys/fs/bpf/sk_storage
  {"error":"can't dump sk_storage map: Unknown error 524"}

An error later in the walk is now reported too, as "bpftool map getnext"
reports it, and the element count is printed only after a complete walk.

Assisted-by: LLM
Signed-off-by: Maxim Skokov <skokovmaksimevg@gmail.com>
---
 tools/bpf/bpftool/map.c | 26 ++++++++++++++++++++++++--
 1 file changed, 24 insertions(+), 2 deletions(-)

diff --git a/tools/bpf/bpftool/map.c b/tools/bpf/bpftool/map.c
index 20d59eab09..626882f207 100644
--- a/tools/bpf/bpftool/map.c
+++ b/tools/bpf/bpftool/map.c
@@ -856,6 +856,25 @@ map_dump(int fd, struct bpf_map_info *info, json_writer_t *wtr,
 		}
 	}
 
+	/*
+	 * Map types that can't be iterated fail on the very first key. Say so
+	 * before printing anything, so that the output isn't an empty dump.
+	 */
+	if (bpf_map_get_next_key(fd, NULL, key) && errno != ENOENT) {
+		const char *map_type_str;
+		int saved_errno = errno;
+
+		map_type_str = libbpf_bpf_map_type_str(info->type);
+		if (map_type_str)
+			p_err("can't dump %s map: %s", map_type_str,
+			      strerror(saved_errno));
+		else
+			p_err("can't dump map of type %u: %s", info->type,
+			      strerror(saved_errno));
+		err = -1;
+		goto exit_free;
+	}
+
 	if (wtr) {
 		err = get_map_kv_btf(info, &btf);
 		if (err) {
@@ -883,8 +902,11 @@ map_dump(int fd, struct bpf_map_info *info, json_writer_t *wtr,
 	while (true) {
 		err = bpf_map_get_next_key(fd, prev_key, key);
 		if (err) {
-			if (errno == ENOENT)
+			if (errno == ENOENT) {
 				err = 0;
+				break;
+			}
+			p_err("can't get next key: %s", strerror(errno));
 			break;
 		}
 		if (!dump_map_elem(fd, key, value, info, btf, wtr,
@@ -897,7 +919,7 @@ map_dump(int fd, struct bpf_map_info *info, json_writer_t *wtr,
 		jsonw_end_array(wtr);	/* elements */
 		if (show_header)
 			jsonw_end_object(wtr);	/* map object */
-	} else {
+	} else if (!err) {
 		printf("Found %u element%s\n", num_elems,
 		       num_elems != 1 ? "s" : "");
 	}
-- 
2.47.3


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [PATCH bpf-next v2 2/2] selftests/bpf: Check that bpftool map dump fails for socket storage
  2026-09-25 16:20 [PATCH bpf-next v2 0/2] bpftool: Fail map dump for maps that can't be iterated Maxim Skokov
  2026-09-25 16:20 ` [PATCH bpf-next v2 1/2] " Maxim Skokov
@ 2026-09-25 16:20 ` Maxim Skokov
  1 sibling, 0 replies; 5+ messages in thread
From: Maxim Skokov @ 2026-09-25 16:20 UTC (permalink / raw)
  To: bpf
  Cc: qmo, ast, daniel, andrii, eddyz87, memxor, martin.lau, song,
	yonghong.song, jolsa, emil, ihor.solodrai, kuba, shuah,
	linux-kselftest, Maxim Skokov

Socket storage can't be iterated from user space. Create a
BPF_MAP_TYPE_SK_STORAGE map, attach storage to a socket, pin it, and
check that "bpftool map dump" fails with nothing but an error: a JSON
error object with -j, no elements and no count on stdout in plain mode,
and the reason on stderr.

Assisted-by: LLM
Signed-off-by: Maxim Skokov <skokovmaksimevg@gmail.com>
---
 .../bpf/prog_tests/bpftool_map_dump.c         | 88 +++++++++++++++++++
 1 file changed, 88 insertions(+)
 create mode 100644 tools/testing/selftests/bpf/prog_tests/bpftool_map_dump.c

diff --git a/tools/testing/selftests/bpf/prog_tests/bpftool_map_dump.c b/tools/testing/selftests/bpf/prog_tests/bpftool_map_dump.c
new file mode 100644
index 0000000000..0a0fa8926b
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/bpftool_map_dump.c
@@ -0,0 +1,88 @@
+// SPDX-License-Identifier: GPL-2.0-only
+#include <sys/socket.h>
+#include <unistd.h>
+#include <bpf/btf.h>
+#include <test_progs.h>
+#include "bpftool_helpers.h"
+
+#define PIN_PATH	"/sys/fs/bpf/test_bpftool_map_dump_sk_storage"
+#define JSON_ERROR	"{\"error\":\"can't dump sk_storage map"
+
+/* A socket storage map with a single element, keyed on a socket we own. */
+static int create_sk_storage_map(void)
+{
+	LIBBPF_OPTS(bpf_map_create_opts, opts,
+		    .map_flags = BPF_F_NO_PREALLOC);
+	struct btf *btf;
+	int int_id, fd = -1;
+
+	btf = btf__new_empty();
+	if (!ASSERT_OK_PTR(btf, "btf__new_empty"))
+		return -1;
+
+	int_id = btf__add_int(btf, "int", 4, BTF_INT_SIGNED);
+	if (!ASSERT_GT(int_id, 0, "btf__add_int"))
+		goto out;
+	if (!ASSERT_OK(btf__load_into_kernel(btf), "btf__load_into_kernel"))
+		goto out;
+
+	opts.btf_fd = btf__fd(btf);
+	opts.btf_key_type_id = int_id;
+	opts.btf_value_type_id = int_id;
+	fd = bpf_map_create(BPF_MAP_TYPE_SK_STORAGE, "sk_storage", sizeof(int),
+			    sizeof(int), 0, &opts);
+	ASSERT_OK_FD(fd, "bpf_map_create");
+out:
+	btf__free(btf);
+	return fd;
+}
+
+void test_bpftool_map_dump(void)
+{
+	int map_fd, sk_fd = -1, value = 42, ret;
+	char output[1024] = {};
+
+	map_fd = create_sk_storage_map();
+	if (map_fd < 0)
+		return;
+
+	sk_fd = socket(AF_INET, SOCK_STREAM, 0);
+	if (!ASSERT_OK_FD(sk_fd, "socket"))
+		goto out;
+	if (!ASSERT_OK(bpf_map_update_elem(map_fd, &sk_fd, &value, BPF_NOEXIST),
+		       "add socket storage"))
+		goto out;
+	if (!ASSERT_OK(bpf_obj_pin(map_fd, PIN_PATH), "pin map"))
+		goto out;
+
+	/*
+	 * Socket storage can't be iterated, so the dump must fail with an
+	 * error and nothing else, rather than print an empty map that holds
+	 * an element. The buffer is cleared before each run so that a run
+	 * that fails to start can't match stale or uninitialised output.
+	 */
+	ret = get_bpftool_command_output("-j map dump pinned " PIN_PATH,
+					 output, sizeof(output));
+	ASSERT_NEQ(ret, 0, "json dump fails");
+	ASSERT_STRNEQ(output, JSON_ERROR, strlen(JSON_ERROR),
+		      "json dump is only the error");
+
+	memset(output, 0, sizeof(output));
+	ret = get_bpftool_command_output("map dump pinned " PIN_PATH,
+					 output, sizeof(output));
+	ASSERT_NEQ(ret, 0, "plain dump fails");
+	ASSERT_NULL(strchr(output, '['), "plain dump prints no elements");
+	ASSERT_NULL(strstr(output, "Found"), "plain dump prints no count");
+
+	memset(output, 0, sizeof(output));
+	get_bpftool_command_output("map dump pinned " PIN_PATH " 2>&1 >/dev/null",
+				   output, sizeof(output));
+	ASSERT_HAS_SUBSTR(output, "can't dump sk_storage map",
+			  "plain dump explains why on stderr");
+
+	unlink(PIN_PATH);
+out:
+	if (sk_fd >= 0)
+		close(sk_fd);
+	close(map_fd);
+}
-- 
2.47.3


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* Re: [PATCH bpf-next v2 1/2] bpftool: Fail map dump for maps that can't be iterated
  2026-09-25 16:20 ` [PATCH bpf-next v2 1/2] " Maxim Skokov
@ 2026-09-25 19:24   ` Quentin Monnet
  2026-09-26  1:44     ` Tianyi Chen
  0 siblings, 1 reply; 5+ messages in thread
From: Quentin Monnet @ 2026-09-25 19:24 UTC (permalink / raw)
  To: Maxim Skokov, bpf, Tianyi Chen
  Cc: ast, daniel, andrii, eddyz87, memxor, martin.lau, song,
	yonghong.song, jolsa, emil, ihor.solodrai, kuba, shuah,
	linux-kselftest

2026-09-25 19:20 UTC+0300 ~ Maxim Skokov <skokovmaksimevg@gmail.com>
> "bpftool map dump" walks the map with bpf_map_get_next_key() and treats
> ENOENT as the end of the map. Map types that can't be iterated fail that
> call on the very first key: local storage and ringbuf with ENOTSUPP,
> bloom filter and arena with EOPNOTSUPP, queue and stack with EINVAL.
> bpftool then ends the walk without a word and prints what it prints for
> an empty map, "[]" with BTF or "Found 0 elements" without, and only the
> exit status tells that the map was not read. On a socket storage map
> that holds storage for a socket:
> 
>   # bpftool map dump pinned /sys/fs/bpf/sk_storage
>   []
> 
> Ask for the first key before printing anything, and when that fails with
> anything but ENOENT, fail with the map type and the error instead of a
> dump:
> 
>   # bpftool map dump pinned /sys/fs/bpf/sk_storage
>   Error: can't dump sk_storage map: Unknown error 524
>   # bpftool -j map dump pinned /sys/fs/bpf/sk_storage
>   {"error":"can't dump sk_storage map: Unknown error 524"}
> 
> An error later in the walk is now reported too, as "bpftool map getnext"
> reports it, and the element count is printed only after a complete walk.
> 
> Assisted-by: LLM
> Signed-off-by: Maxim Skokov <skokovmaksimevg@gmail.com>
> ---
>  tools/bpf/bpftool/map.c | 26 ++++++++++++++++++++++++--
>  1 file changed, 24 insertions(+), 2 deletions(-)
> 
> diff --git a/tools/bpf/bpftool/map.c b/tools/bpf/bpftool/map.c
> index 20d59eab09..626882f207 100644
> --- a/tools/bpf/bpftool/map.c
> +++ b/tools/bpf/bpftool/map.c
> @@ -856,6 +856,25 @@ map_dump(int fd, struct bpf_map_info *info, json_writer_t *wtr,
>  		}
>  	}
>  
> +	/*
> +	 * Map types that can't be iterated fail on the very first key. Say so
> +	 * before printing anything, so that the output isn't an empty dump.
> +	 */


Or simply:

  /* Fail early to avoid an empty dump if map type cannot be iterated */

But that's a minor nit, the patch looks good to me, thank you!

Acked-by: Quentin Monnet <qmo@kernel.org>

Looks like you need to address Sashiko's findings on patch 2, though.


> +	if (bpf_map_get_next_key(fd, NULL, key) && errno != ENOENT) {
> +		const char *map_type_str;
> +		int saved_errno = errno;
> +
> +		map_type_str = libbpf_bpf_map_type_str(info->type);
> +		if (map_type_str)
> +			p_err("can't dump %s map: %s", map_type_str,
> +			      strerror(saved_errno));
> +		else
> +			p_err("can't dump map of type %u: %s", info->type,
> +			      strerror(saved_errno));
> +		err = -1;
> +		goto exit_free;
> +	}


Tianyi, note that this conflicts with your series ("bpftool: Add
recursive map dumping"), one of you will have to rebase.


> +
>  	if (wtr) {
>  		err = get_map_kv_btf(info, &btf);
>  		if (err) {
> @@ -883,8 +902,11 @@ map_dump(int fd, struct bpf_map_info *info, json_writer_t *wtr,
>  	while (true) {
>  		err = bpf_map_get_next_key(fd, prev_key, key);
>  		if (err) {
> -			if (errno == ENOENT)
> +			if (errno == ENOENT) {
>  				err = 0;
> +				break;
> +			}
> +			p_err("can't get next key: %s", strerror(errno));
>  			break;
>  		}
>  		if (!dump_map_elem(fd, key, value, info, btf, wtr,
> @@ -897,7 +919,7 @@ map_dump(int fd, struct bpf_map_info *info, json_writer_t *wtr,
>  		jsonw_end_array(wtr);	/* elements */
>  		if (show_header)
>  			jsonw_end_object(wtr);	/* map object */
> -	} else {
> +	} else if (!err) {
>  		printf("Found %u element%s\n", num_elems,
>  		       num_elems != 1 ? "s" : "");
>  	}


^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH bpf-next v2 1/2] bpftool: Fail map dump for maps that can't be iterated
  2026-09-25 19:24   ` Quentin Monnet
@ 2026-09-26  1:44     ` Tianyi Chen
  0 siblings, 0 replies; 5+ messages in thread
From: Tianyi Chen @ 2026-09-26  1:44 UTC (permalink / raw)
  To: qmo, skokovmaksimevg
  Cc: bpf, ast, daniel, andrii, eddyz87, memxor, martin.lau, song,
	yonghong.song, jolsa, emil, ihor.solodrai, kuba, shuah,
	linux-kselftest

Hi Quentin, Maxim,

> Tianyi, note that this conflicts with your series ("bpftool: Add
> recursive map dumping"), one of you will have to rebase.

I'll take care of rebasing the recursive dump series. I have posted v9
on current bpf-next plus Maxim's v2 patch 1/2, explicitly listed as a
prerequisite in the cover letter:
https://lore.kernel.org/r/20260926014208.24773-1-hi@tychen.cc

The conflict resolution keeps the early iteration check before recursive
plain/BTF output and retains Maxim's iteration-error reporting. All 15
recursive dump subtests pass on the combined tree. I also checked that
non-iterable ringbuf dumps fail with valid JSON, with and without recursion.

Maxim, please go ahead with your revisions; no need to rebase around my
series. I'll adjust mine if your implementation changes.

Thanks,
Tianyi

^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2026-09-26  1:44 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-25 16:20 [PATCH bpf-next v2 0/2] bpftool: Fail map dump for maps that can't be iterated Maxim Skokov
2026-09-25 16:20 ` [PATCH bpf-next v2 1/2] " Maxim Skokov
2026-09-25 19:24   ` Quentin Monnet
2026-09-26  1:44     ` Tianyi Chen
2026-09-25 16:20 ` [PATCH bpf-next v2 2/2] selftests/bpf: Check that bpftool map dump fails for socket storage Maxim Skokov

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox