> diff --git a/tools/bpf/bpftool/Documentation/bpftool-feature.rst 
> b/tools/bpf/bpftool/Documentation/bpftool-feature.rst
> index c7f837898bc7..597f2426d255 100644
> --- a/tools/bpf/bpftool/Documentation/bpftool-feature.rst
> +++ b/tools/bpf/bpftool/Documentation/bpftool-feature.rst

[ ... ]

> @@ -68,12 +68,14 @@ bpftool feature probe dev *NAME* [full] [macros [prefix 
> *PREFIX*]]
>
>  bpftool feature list_builtins *GROUP*
>      List items known to bpftool. These can be BPF program types
> -    (**prog_types**), BPF map types (**map_types**), attach types
> +    (**prog_types**), BPF map types (**map_types**), map creation flags
> +    (**map_create_flags**), attach types
>      (**attach_types**), link types (**link_types**), or BPF helper functions
>      (**helpers**). The command does not probe the system, but simply lists 
> the
>      elements that bpftool knows from compilation time, as provided from 
> libbpf
> -    (for all object types) or from the BPF UAPI header (list of helpers). 
> This
> -    can be used in scripts to iterate over BPF types or helpers.
> +    (for all object types) or from the BPF UAPI header (helpers and map 
> creation
> +    flags). This can be used in scripts to iterate over BPF types, helpers, 
> or
> +    the symbolic flags accepted by **map create**.

This isn't a bug, but could this paragraph be re-wrapped so the new
group folds in at the same width as the surrounding lines? The second
half was re-flowed to match the file's usual 78-column width, but the
first half wasn't, leaving "(**map_create_flags**), attach types" about
half as wide as the lines around it.

> diff --git a/tools/bpf/bpftool/Documentation/bpftool-map.rst 
> b/tools/bpf/bpftool/Documentation/bpftool-map.rst
> index 5daf3de5c744..5a5c26f789cd 100644
> --- a/tools/bpf/bpftool/Documentation/bpftool-map.rst
> +++ b/tools/bpf/bpftool/Documentation/bpftool-map.rst

[ ... ]

> diff --git a/tools/bpf/bpftool/bash-completion/bpftool 
> b/tools/bpf/bpftool/bash-completion/bpftool
> index 75cbcb512eba..ea3f521b6f08 100644
> --- a/tools/bpf/bpftool/bash-completion/bpftool
> +++ b/tools/bpf/bpftool/bash-completion/bpftool

[ ... ]

> diff --git a/tools/bpf/bpftool/feature.c b/tools/bpf/bpftool/feature.c
> index 0f6070a0c8e7..b5419d5a661a 100644
> --- a/tools/bpf/bpftool/feature.c
> +++ b/tools/bpf/bpftool/feature.c

[ ... ]

> diff --git a/tools/bpf/bpftool/main.h b/tools/bpf/bpftool/main.h
> index 9315a1db1f7c..3e2856c9f595 100644
> --- a/tools/bpf/bpftool/main.h
> +++ b/tools/bpf/bpftool/main.h

[ ... ]

> diff --git a/tools/bpf/bpftool/map.c b/tools/bpf/bpftool/map.c
> index 684a8fb72414..4445014247d6 100644
> --- a/tools/bpf/bpftool/map.c
> +++ b/tools/bpf/bpftool/map.c
> @@ -1250,6 +1250,81 @@ static int do_pin(int argc, char **argv)
>       return err;
>  }
>
> +static const struct {
> +     const char *name;
> +     __u32 value;
> +} map_create_flags[] = {
> +#define MAP_CREATE_FLAG(flag) { #flag, flag }
> +     MAP_CREATE_FLAG(BPF_F_NO_PREALLOC),
> +     MAP_CREATE_FLAG(BPF_F_NO_COMMON_LRU),
> +     MAP_CREATE_FLAG(BPF_F_NUMA_NODE),
> +     MAP_CREATE_FLAG(BPF_F_RDONLY),
> +     MAP_CREATE_FLAG(BPF_F_WRONLY),
> +     MAP_CREATE_FLAG(BPF_F_STACK_BUILD_ID),
> +     MAP_CREATE_FLAG(BPF_F_ZERO_SEED),
> +     MAP_CREATE_FLAG(BPF_F_RDONLY_PROG),
> +     MAP_CREATE_FLAG(BPF_F_WRONLY_PROG),
> +     MAP_CREATE_FLAG(BPF_F_CLONE),
> +     MAP_CREATE_FLAG(BPF_F_MMAPABLE),
> +     MAP_CREATE_FLAG(BPF_F_PRESERVE_ELEMS),
> +     MAP_CREATE_FLAG(BPF_F_INNER_MAP),
> +     MAP_CREATE_FLAG(BPF_F_LINK),
> +     MAP_CREATE_FLAG(BPF_F_VTYPE_BTF_OBJ_FD),
> +     MAP_CREATE_FLAG(BPF_F_TOKEN_FD),
> +     MAP_CREATE_FLAG(BPF_F_SEGV_ON_FAULT),
> +     MAP_CREATE_FLAG(BPF_F_NO_USER_CONV),
> +     MAP_CREATE_FLAG(BPF_F_RB_OVERWRITE),
> +#undef MAP_CREATE_FLAG
> +};

This isn't a bug, but the table walks bits 0-19 of the map creation
flags in include/uapi/linux/bpf.h in order, except bit 14
(BPF_F_PATH_FD) is left out between BPF_F_LINK and BPF_F_VTYPE_BTF_OBJ_FD,
with nothing in the source recording why.

The omission looks deliberate (BPF_F_PATH_FD's comment says it is for
BPF_OBJ_PIN/BPF_OBJ_GET, and the follow-up selftest asserts that
`flags BPF_F_PATH_FD` is rejected), but nothing marks it as intentional,
so the next person adding a flag to that enum has no way to tell a
skipped bit from a forgotten one.

The wording added to bpftool-map.rst - "full, case-sensitive map
creation flag names from the bpf.h UAPI header" - also reads as
covering the whole enum, so a user who finds BPF_F_PATH_FD there and
passes it by name gets "can't parse" while `flags 16384` is accepted.

Would a one-line comment next to the gap (something like
"BPF_F_PATH_FD is an OBJ_PIN/OBJ_GET flag, not a map creation flag")
help, and should the manpage wording narrow "from the bpf.h UAPI
header" a little so BPF_F_PATH_FD doesn't look like an accepted name?

> +
> +const char *map_create_flag_name(unsigned int id)
> +{
> +     if (id >= ARRAY_SIZE(map_create_flags))
> +             return NULL;
> +
> +     return map_create_flags[id].name;
> +}
> +
> +static int parse_map_create_flags(const char *arg, __u32 *flags)
> +{
> +     const char *name = arg, *comma;
> +     long long value;
> +     __u32 parsed = 0;
> +     size_t len, i;
> +     char *end;
> +
> +     /* Keep base-0 numeric input, including bits unknown to this bpftool. */
> +     if (strncmp(arg, "BPF_F_", 6)) {
> +             errno = 0;
> +             value = strtoll(arg, &end, 0);
> +             if (errno || end == arg || *end || value < 0 || value > 
> UINT32_MAX)
> +                     goto invalid;
> +             *flags = value;
> +             return 0;
> +     }
> +
> +     do {
> +             comma = strchr(name, ',');
> +             len = comma ? (size_t)(comma - name) : strlen(name);
> +             for (i = 0; i < ARRAY_SIZE(map_create_flags); i++) {
> +                     if (strlen(map_create_flags[i].name) == len &&
> +                         !strncmp(name, map_create_flags[i].name, len))
> +                             break;
> +             }
> +             if (i == ARRAY_SIZE(map_create_flags))
> +                     goto invalid;
> +             parsed |= map_create_flags[i].value;
> +             if (comma)
> +                     name = comma + 1;
> +     } while (comma);
> +
> +     *flags = parsed;
> +     return 0;
> +invalid:
> +     p_err("can't parse %s as map creation flags", arg);
> +     return -1;
> +}

[ ... ]

The changelog presents the new rejections as added validation, but
three of them are inputs the old parser accepted, so they are a
user-visible behaviour change rather than new strictness.

The previous branch was parse_u32_arg() -> strtoul(**argv, &endptr, 0)
with only `if (*endptr)` checked, so `flags ""` quietly became 0,
`flags 0x100000000` quietly truncated to 0, and `flags -1` quietly
wrapped to 0xffffffff. The new strtoll() path rejects all three.

That compatibility note is the part a reviewer would most want spelled
out, and it is the one thing the message leaves implicit while the
closing paragraphs restate the documentation and bash-completion hunks.

Could the changelog say that these numeric forms were previously
accepted (empty string and out-of-range values were silently
truncated), so the tightening is visible as an intentional behaviour
change?


---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md

CI run summary: https://github.com/kernel-patches/bpf/actions/runs/34557157318

Reply via email to