* [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
* 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
* [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
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