Commit 3566a79c authored by Jakub Kicinski's avatar Jakub Kicinski

Merge tag 'for-netdev' of git://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next

Daniel Borkmann says:

====================
pull-request: bpf-next 2022-10-18

We've added 33 non-merge commits during the last 14 day(s) which contain
a total of 31 files changed, 874 insertions(+), 538 deletions(-).

The main changes are:

1) Add RCU grace period chaining to BPF to wait for the completion
   of access from both sleepable and non-sleepable BPF programs,
   from Hou Tao & Paul E. McKenney.

2) Improve helper UAPI by explicitly defining BPF_FUNC_xxx integer
   values. In the wild we have seen OS vendors doing buggy backports
   where helper call numbers mismatched. This is an attempt to make
   backports more foolproof, from Andrii Nakryiko.

3) Add libbpf *_opts API-variants for bpf_*_get_fd_by_id() functions,
   from Roberto Sassu.

4) Fix libbpf's BTF dumper for structs with padding-only fields,
   from Eduard Zingerman.

5) Fix various libbpf bugs which have been found from fuzzing with
   malformed BPF object files, from Shung-Hsi Yu.

6) Clean up an unneeded check on existence of SSE2 in BPF x86-64 JIT,
   from Jie Meng.

7) Fix various ASAN bugs in both libbpf and selftests when running
   the BPF selftest suite on arm64, from Xu Kuohai.

8) Fix missing bpf_iter_vma_offset__destroy() call in BPF iter selftest
   and use in-skeleton link pointer to remove an explicit bpf_link__destroy(),
   from Jiri Olsa.

9) Fix BPF CI breakage by pointing to iptables-legacy instead of relying
   on symlinked iptables which got upgraded to iptables-nft,
   from Martin KaFai Lau.

10) Minor BPF selftest improvements all over the place, from various others.

* tag 'for-netdev' of git://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next: (33 commits)
  bpf/docs: Update README for most recent vmtest.sh
  bpf: Use rcu_trace_implies_rcu_gp() for program array freeing
  bpf: Use rcu_trace_implies_rcu_gp() in local storage map
  bpf: Use rcu_trace_implies_rcu_gp() in bpf memory allocator
  rcu-tasks: Provide rcu_trace_implies_rcu_gp()
  selftests/bpf: Use sys_pidfd_open() helper when possible
  libbpf: Fix null-pointer dereference in find_prog_by_sec_insn()
  libbpf: Deal with section with no data gracefully
  libbpf: Use elf_getshdrnum() instead of e_shnum
  selftest/bpf: Fix error usage of ASSERT_OK in xdp_adjust_tail.c
  selftests/bpf: Fix error failure of case test_xdp_adjust_tail_grow
  selftest/bpf: Fix memory leak in kprobe_multi_test
  selftests/bpf: Fix memory leak caused by not destroying skeleton
  libbpf: Fix memory leak in parse_usdt_arg()
  libbpf: Fix use-after-free in btf_dump_name_dups
  selftests/bpf: S/iptables/iptables-legacy/ in the bpf_nf and xdp_synproxy test
  selftests/bpf: Alphabetize DENYLISTs
  selftests/bpf: Add tests for _opts variants of bpf_*_get_fd_by_id()
  libbpf: Introduce bpf_link_get_fd_by_id_opts()
  libbpf: Introduce bpf_btf_get_fd_by_id_opts()
  ...
====================

Link: https://lore.kernel.org/r/20221018210631.11211-1-daniel@iogearbox.netSigned-off-by: default avatarJakub Kicinski <kuba@kernel.org>
parents f00909e2 6c4e777f
......@@ -1226,8 +1226,7 @@ static int do_jit(struct bpf_prog *bpf_prog, int *addrs, u8 *image, u8 *rw_image
/* speculation barrier */
case BPF_ST | BPF_NOSPEC:
if (boot_cpu_has(X86_FEATURE_XMM2))
EMIT_LFENCE();
EMIT_LFENCE();
break;
/* ST: *(u8*)(dst_reg + off) = imm */
......
......@@ -240,6 +240,18 @@ static inline void exit_tasks_rcu_start(void) { }
static inline void exit_tasks_rcu_finish(void) { }
#endif /* #else #ifdef CONFIG_TASKS_RCU_GENERIC */
/**
* rcu_trace_implies_rcu_gp - does an RCU Tasks Trace grace period imply an RCU grace period?
*
* As an accident of implementation, an RCU Tasks Trace grace period also
* acts as an RCU grace period. However, this could change at any time.
* Code relying on this accident must call this function to verify that
* this accident is still happening.
*
* You have been warned!
*/
static inline bool rcu_trace_implies_rcu_gp(void) { return true; }
/**
* cond_resched_tasks_rcu_qs - Report potential quiescent states to RCU
*
......
This diff is collapsed.
......@@ -88,8 +88,14 @@ void bpf_local_storage_free_rcu(struct rcu_head *rcu)
{
struct bpf_local_storage *local_storage;
/* If RCU Tasks Trace grace period implies RCU grace period, do
* kfree(), else do kfree_rcu().
*/
local_storage = container_of(rcu, struct bpf_local_storage, rcu);
kfree_rcu(local_storage, rcu);
if (rcu_trace_implies_rcu_gp())
kfree(local_storage);
else
kfree_rcu(local_storage, rcu);
}
static void bpf_selem_free_rcu(struct rcu_head *rcu)
......@@ -97,7 +103,10 @@ static void bpf_selem_free_rcu(struct rcu_head *rcu)
struct bpf_local_storage_elem *selem;
selem = container_of(rcu, struct bpf_local_storage_elem, rcu);
kfree_rcu(selem, rcu);
if (rcu_trace_implies_rcu_gp())
kfree(selem);
else
kfree_rcu(selem, rcu);
}
/* local_storage->lock must be held and selem->local_storage == local_storage.
......
......@@ -2251,8 +2251,14 @@ static void __bpf_prog_array_free_sleepable_cb(struct rcu_head *rcu)
{
struct bpf_prog_array *progs;
/* If RCU Tasks Trace grace period implies RCU grace period, there is
* no need to call kfree_rcu(), just call kfree() directly.
*/
progs = container_of(rcu, struct bpf_prog_array, rcu);
kfree_rcu(progs, rcu);
if (rcu_trace_implies_rcu_gp())
kfree(progs);
else
kfree_rcu(progs, rcu);
}
void bpf_prog_array_free_sleepable(struct bpf_prog_array *progs)
......
......@@ -222,9 +222,13 @@ static void __free_rcu(struct rcu_head *head)
static void __free_rcu_tasks_trace(struct rcu_head *head)
{
struct bpf_mem_cache *c = container_of(head, struct bpf_mem_cache, rcu);
call_rcu(&c->rcu, __free_rcu);
/* If RCU Tasks Trace grace period implies RCU grace period,
* there is no need to invoke call_rcu().
*/
if (rcu_trace_implies_rcu_gp())
__free_rcu(head);
else
call_rcu(head, __free_rcu);
}
static void enque_to_free(struct bpf_mem_cache *c, void *obj)
......@@ -253,8 +257,9 @@ static void do_call_rcu(struct bpf_mem_cache *c)
*/
__llist_add(llnode, &c->waiting_for_gp);
/* Use call_rcu_tasks_trace() to wait for sleepable progs to finish.
* Then use call_rcu() to wait for normal progs to finish
* and finally do free_one() on each element.
* If RCU Tasks Trace grace period implies RCU grace period, free
* these elements directly, else use call_rcu() to wait for normal
* progs to finish and finally do free_one() on each element.
*/
call_rcu_tasks_trace(&c->rcu, __free_rcu_tasks_trace);
}
......
......@@ -1535,6 +1535,8 @@ static void rcu_tasks_trace_postscan(struct list_head *hop)
{
// Wait for late-stage exiting tasks to finish exiting.
// These might have passed the call to exit_tasks_rcu_finish().
// If you remove the following line, update rcu_trace_implies_rcu_gp()!!!
synchronize_rcu();
// Any tasks that exit after this point will set
// TRC_NEED_QS_CHECKED in ->trc_reader_special.b.need_qs.
......
......@@ -97,6 +97,7 @@ class HeaderParser(object):
self.desc_unique_helpers = set()
self.define_unique_helpers = []
self.helper_enum_vals = {}
self.helper_enum_pos = {}
self.desc_syscalls = []
self.enum_syscalls = []
......@@ -253,54 +254,71 @@ class HeaderParser(object):
break
def parse_define_helpers(self):
# Parse FN(...) in #define __BPF_FUNC_MAPPER to compare later with the
# Parse FN(...) in #define ___BPF_FUNC_MAPPER to compare later with the
# number of unique function names present in description and use the
# correct enumeration value.
# Note: seek_to(..) discards the first line below the target search text,
# resulting in FN(unspec) being skipped and not added to self.define_unique_helpers.
self.seek_to('#define __BPF_FUNC_MAPPER(FN)',
# resulting in FN(unspec, 0, ##ctx) being skipped and not added to
# self.define_unique_helpers.
self.seek_to('#define ___BPF_FUNC_MAPPER(FN, ctx...)',
'Could not find start of eBPF helper definition list')
# Searches for one FN(\w+) define or a backslash for newline
p = re.compile('\s*FN\((\w+)\)|\\\\')
p = re.compile('\s*FN\((\w+), (\d+), ##ctx\)|\\\\')
fn_defines_str = ''
i = 1 # 'unspec' is skipped as mentioned above
i = 0
while True:
capture = p.match(self.line)
if capture:
fn_defines_str += self.line
self.helper_enum_vals[capture.expand(r'bpf_\1')] = i
helper_name = capture.expand(r'bpf_\1')
self.helper_enum_vals[helper_name] = int(capture[2])
self.helper_enum_pos[helper_name] = i
i += 1
else:
break
self.line = self.reader.readline()
# Find the number of occurences of FN(\w+)
self.define_unique_helpers = re.findall('FN\(\w+\)', fn_defines_str)
self.define_unique_helpers = re.findall('FN\(\w+, \d+, ##ctx\)', fn_defines_str)
def assign_helper_values(self):
def validate_helpers(self):
last_helper = ''
seen_helpers = set()
seen_enum_vals = set()
i = 0
for helper in self.helpers:
proto = helper.proto_break_down()
name = proto['name']
try:
enum_val = self.helper_enum_vals[name]
enum_pos = self.helper_enum_pos[name]
except KeyError:
raise Exception("Helper %s is missing from enum bpf_func_id" % name)
if name in seen_helpers:
if last_helper != name:
raise Exception("Helper %s has multiple descriptions which are not grouped together" % name)
continue
# Enforce current practice of having the descriptions ordered
# by enum value.
if enum_pos != i:
raise Exception("Helper %s (ID %d) comment order (#%d) must be aligned with its position (#%d) in enum bpf_func_id" % (name, enum_val, i + 1, enum_pos + 1))
if enum_val in seen_enum_vals:
raise Exception("Helper %s has duplicated value %d" % (name, enum_val))
seen_helpers.add(name)
desc_val = len(seen_helpers)
if desc_val != enum_val:
raise Exception("Helper %s comment order (#%d) must be aligned with its position (#%d) in enum bpf_func_id" % (name, desc_val, enum_val))
last_helper = name
seen_enum_vals.add(enum_val)
helper.enum_val = enum_val
i += 1
def run(self):
self.parse_desc_syscall()
self.parse_enum_syscall()
self.parse_desc_helpers()
self.parse_define_helpers()
self.assign_helper_values()
self.validate_helpers()
self.reader.close()
###############################################################################
......@@ -423,7 +441,7 @@ class PrinterHelpersRST(PrinterRST):
"""
def __init__(self, parser):
self.elements = parser.helpers
self.elem_number_check(parser.desc_unique_helpers, parser.define_unique_helpers, 'helper', '__BPF_FUNC_MAPPER')
self.elem_number_check(parser.desc_unique_helpers, parser.define_unique_helpers, 'helper', '___BPF_FUNC_MAPPER')
def print_header(self):
header = '''\
......@@ -636,7 +654,7 @@ class PrinterHelpers(Printer):
"""
def __init__(self, parser):
self.elements = parser.helpers
self.elem_number_check(parser.desc_unique_helpers, parser.define_unique_helpers, 'helper', '__BPF_FUNC_MAPPER')
self.elem_number_check(parser.desc_unique_helpers, parser.define_unique_helpers, 'helper', '___BPF_FUNC_MAPPER')
type_fwds = [
'struct bpf_fib_lookup',
......
This diff is collapsed.
......@@ -935,58 +935,98 @@ int bpf_link_get_next_id(__u32 start_id, __u32 *next_id)
return bpf_obj_get_next_id(start_id, next_id, BPF_LINK_GET_NEXT_ID);
}
int bpf_prog_get_fd_by_id(__u32 id)
int bpf_prog_get_fd_by_id_opts(__u32 id,
const struct bpf_get_fd_by_id_opts *opts)
{
const size_t attr_sz = offsetofend(union bpf_attr, open_flags);
union bpf_attr attr;
int fd;
if (!OPTS_VALID(opts, bpf_get_fd_by_id_opts))
return libbpf_err(-EINVAL);
memset(&attr, 0, attr_sz);
attr.prog_id = id;
attr.open_flags = OPTS_GET(opts, open_flags, 0);
fd = sys_bpf_fd(BPF_PROG_GET_FD_BY_ID, &attr, attr_sz);
return libbpf_err_errno(fd);
}
int bpf_map_get_fd_by_id(__u32 id)
int bpf_prog_get_fd_by_id(__u32 id)
{
return bpf_prog_get_fd_by_id_opts(id, NULL);
}
int bpf_map_get_fd_by_id_opts(__u32 id,
const struct bpf_get_fd_by_id_opts *opts)
{
const size_t attr_sz = offsetofend(union bpf_attr, open_flags);
union bpf_attr attr;
int fd;
if (!OPTS_VALID(opts, bpf_get_fd_by_id_opts))
return libbpf_err(-EINVAL);
memset(&attr, 0, attr_sz);
attr.map_id = id;
attr.open_flags = OPTS_GET(opts, open_flags, 0);
fd = sys_bpf_fd(BPF_MAP_GET_FD_BY_ID, &attr, attr_sz);
return libbpf_err_errno(fd);
}
int bpf_btf_get_fd_by_id(__u32 id)
int bpf_map_get_fd_by_id(__u32 id)
{
return bpf_map_get_fd_by_id_opts(id, NULL);
}
int bpf_btf_get_fd_by_id_opts(__u32 id,
const struct bpf_get_fd_by_id_opts *opts)
{
const size_t attr_sz = offsetofend(union bpf_attr, open_flags);
union bpf_attr attr;
int fd;
if (!OPTS_VALID(opts, bpf_get_fd_by_id_opts))
return libbpf_err(-EINVAL);
memset(&attr, 0, attr_sz);
attr.btf_id = id;
attr.open_flags = OPTS_GET(opts, open_flags, 0);
fd = sys_bpf_fd(BPF_BTF_GET_FD_BY_ID, &attr, attr_sz);
return libbpf_err_errno(fd);
}
int bpf_link_get_fd_by_id(__u32 id)
int bpf_btf_get_fd_by_id(__u32 id)
{
return bpf_btf_get_fd_by_id_opts(id, NULL);
}
int bpf_link_get_fd_by_id_opts(__u32 id,
const struct bpf_get_fd_by_id_opts *opts)
{
const size_t attr_sz = offsetofend(union bpf_attr, open_flags);
union bpf_attr attr;
int fd;
if (!OPTS_VALID(opts, bpf_get_fd_by_id_opts))
return libbpf_err(-EINVAL);
memset(&attr, 0, attr_sz);
attr.link_id = id;
attr.open_flags = OPTS_GET(opts, open_flags, 0);
fd = sys_bpf_fd(BPF_LINK_GET_FD_BY_ID, &attr, attr_sz);
return libbpf_err_errno(fd);
}
int bpf_link_get_fd_by_id(__u32 id)
{
return bpf_link_get_fd_by_id_opts(id, NULL);
}
int bpf_obj_get_info_by_fd(int bpf_fd, void *info, __u32 *info_len)
{
const size_t attr_sz = offsetofend(union bpf_attr, info);
......
......@@ -365,10 +365,26 @@ LIBBPF_API int bpf_prog_get_next_id(__u32 start_id, __u32 *next_id);
LIBBPF_API int bpf_map_get_next_id(__u32 start_id, __u32 *next_id);
LIBBPF_API int bpf_btf_get_next_id(__u32 start_id, __u32 *next_id);
LIBBPF_API int bpf_link_get_next_id(__u32 start_id, __u32 *next_id);
struct bpf_get_fd_by_id_opts {
size_t sz; /* size of this struct for forward/backward compatibility */
__u32 open_flags; /* permissions requested for the operation on fd */
size_t :0;
};
#define bpf_get_fd_by_id_opts__last_field open_flags
LIBBPF_API int bpf_prog_get_fd_by_id(__u32 id);
LIBBPF_API int bpf_prog_get_fd_by_id_opts(__u32 id,
const struct bpf_get_fd_by_id_opts *opts);
LIBBPF_API int bpf_map_get_fd_by_id(__u32 id);
LIBBPF_API int bpf_map_get_fd_by_id_opts(__u32 id,
const struct bpf_get_fd_by_id_opts *opts);
LIBBPF_API int bpf_btf_get_fd_by_id(__u32 id);
LIBBPF_API int bpf_btf_get_fd_by_id_opts(__u32 id,
const struct bpf_get_fd_by_id_opts *opts);
LIBBPF_API int bpf_link_get_fd_by_id(__u32 id);
LIBBPF_API int bpf_link_get_fd_by_id_opts(__u32 id,
const struct bpf_get_fd_by_id_opts *opts);
LIBBPF_API int bpf_obj_get_info_by_fd(int bpf_fd, void *info, __u32 *info_len);
struct bpf_prog_query_opts {
......
......@@ -219,6 +219,17 @@ static int btf_dump_resize(struct btf_dump *d)
return 0;
}
static void btf_dump_free_names(struct hashmap *map)
{
size_t bkt;
struct hashmap_entry *cur;
hashmap__for_each_entry(map, cur, bkt)
free((void *)cur->key);
hashmap__free(map);
}
void btf_dump__free(struct btf_dump *d)
{
int i;
......@@ -237,8 +248,8 @@ void btf_dump__free(struct btf_dump *d)
free(d->cached_names);
free(d->emit_queue);
free(d->decl_stack);
hashmap__free(d->type_names);
hashmap__free(d->ident_names);
btf_dump_free_names(d->type_names);
btf_dump_free_names(d->ident_names);
free(d);
}
......@@ -944,7 +955,11 @@ static void btf_dump_emit_struct_def(struct btf_dump *d,
lvl + 1);
}
if (vlen)
/*
* Keep `struct empty {}` on a single line,
* only print newline when there are regular or padding fields.
*/
if (vlen || t->size)
btf_dump_printf(d, "\n");
btf_dump_printf(d, "%s}", pfx(lvl));
if (packed)
......@@ -1520,11 +1535,23 @@ static void btf_dump_emit_type_cast(struct btf_dump *d, __u32 id,
static size_t btf_dump_name_dups(struct btf_dump *d, struct hashmap *name_map,
const char *orig_name)
{
char *old_name, *new_name;
size_t dup_cnt = 0;
int err;
new_name = strdup(orig_name);
if (!new_name)
return 1;
hashmap__find(name_map, orig_name, (void **)&dup_cnt);
dup_cnt++;
hashmap__set(name_map, orig_name, (void *)dup_cnt, NULL, NULL);
err = hashmap__set(name_map, new_name, (void *)dup_cnt,
(const void **)&old_name, NULL);
if (err)
free(new_name);
free(old_name);
return dup_cnt;
}
......
......@@ -597,7 +597,7 @@ struct elf_state {
size_t shstrndx; /* section index for section name strings */
size_t strtabidx;
struct elf_sec_desc *secs;
int sec_cnt;
size_t sec_cnt;
int btf_maps_shndx;
__u32 btf_maps_sec_btf_id;
int text_shndx;
......@@ -1408,6 +1408,10 @@ static int bpf_object__check_endianness(struct bpf_object *obj)
static int
bpf_object__init_license(struct bpf_object *obj, void *data, size_t size)
{
if (!data) {
pr_warn("invalid license section in %s\n", obj->path);
return -LIBBPF_ERRNO__FORMAT;
}
/* libbpf_strlcpy() only copies first N - 1 bytes, so size + 1 won't
* go over allowed ELF data section buffer
*/
......@@ -1421,7 +1425,7 @@ bpf_object__init_kversion(struct bpf_object *obj, void *data, size_t size)
{
__u32 kver;
if (size != sizeof(kver)) {
if (!data || size != sizeof(kver)) {
pr_warn("invalid kver section in %s\n", obj->path);
return -LIBBPF_ERRNO__FORMAT;
}
......@@ -3312,10 +3316,15 @@ static int bpf_object__elf_collect(struct bpf_object *obj)
Elf64_Shdr *sh;
/* ELF section indices are 0-based, but sec #0 is special "invalid"
* section. e_shnum does include sec #0, so e_shnum is the necessary
* size of an array to keep all the sections.
* section. Since section count retrieved by elf_getshdrnum() does
* include sec #0, it is already the necessary size of an array to keep
* all the sections.
*/
obj->efile.sec_cnt = obj->efile.ehdr->e_shnum;
if (elf_getshdrnum(obj->efile.elf, &obj->efile.sec_cnt)) {
pr_warn("elf: failed to get the number of sections for %s: %s\n",
obj->path, elf_errmsg(-1));
return -LIBBPF_ERRNO__FORMAT;
}
obj->efile.secs = calloc(obj->efile.sec_cnt, sizeof(*obj->efile.secs));
if (!obj->efile.secs)
return -ENOMEM;
......@@ -4106,6 +4115,9 @@ static struct bpf_program *find_prog_by_sec_insn(const struct bpf_object *obj,
int l = 0, r = obj->nr_programs - 1, m;
struct bpf_program *prog;
if (!obj->nr_programs)
return NULL;
while (l < r) {
m = l + (r - l + 1) / 2;
prog = &obj->programs[m];
......
......@@ -367,10 +367,14 @@ LIBBPF_1.0.0 {
libbpf_bpf_map_type_str;
libbpf_bpf_prog_type_str;
perf_buffer__buffer;
};
} LIBBPF_0.8.0;
LIBBPF_1.1.0 {
global:
bpf_btf_get_fd_by_id_opts;
bpf_link_get_fd_by_id_opts;
bpf_map_get_fd_by_id_opts;
bpf_prog_get_fd_by_id_opts;
user_ring_buffer__discard;
user_ring_buffer__free;
user_ring_buffer__new;
......
......@@ -1348,25 +1348,23 @@ static int calc_pt_regs_off(const char *reg_name)
static int parse_usdt_arg(const char *arg_str, int arg_num, struct usdt_arg_spec *arg)
{
char *reg_name = NULL;
char reg_name[16];
int arg_sz, len, reg_off;
long off;
if (sscanf(arg_str, " %d @ \[ %m[a-z0-9], %ld ] %n", &arg_sz, &reg_name, &off, &len) == 3) {
if (sscanf(arg_str, " %d @ \[ %15[a-z0-9], %ld ] %n", &arg_sz, reg_name, &off, &len) == 3) {
/* Memory dereference case, e.g., -4@[sp, 96] */
arg->arg_type = USDT_ARG_REG_DEREF;
arg->val_off = off;
reg_off = calc_pt_regs_off(reg_name);
free(reg_name);
if (reg_off < 0)
return reg_off;
arg->reg_off = reg_off;
} else if (sscanf(arg_str, " %d @ \[ %m[a-z0-9] ] %n", &arg_sz, &reg_name, &len) == 2) {
} else if (sscanf(arg_str, " %d @ \[ %15[a-z0-9] ] %n", &arg_sz, reg_name, &len) == 2) {
/* Memory dereference case, e.g., -4@[sp] */
arg->arg_type = USDT_ARG_REG_DEREF;
arg->val_off = 0;
reg_off = calc_pt_regs_off(reg_name);
free(reg_name);
if (reg_off < 0)
return reg_off;
arg->reg_off = reg_off;
......@@ -1375,12 +1373,11 @@ static int parse_usdt_arg(const char *arg_str, int arg_num, struct usdt_arg_spec
arg->arg_type = USDT_ARG_CONST;
arg->val_off = off;
arg->reg_off = 0;
} else if (sscanf(arg_str, " %d @ %m[a-z0-9] %n", &arg_sz, &reg_name, &len) == 2) {
} else if (sscanf(arg_str, " %d @ %15[a-z0-9] %n", &arg_sz, reg_name, &len) == 2) {
/* Register read case, e.g., -8@x4 */
arg->arg_type = USDT_ARG_REG;
arg->val_off = 0;
reg_off = calc_pt_regs_off(reg_name);
free(reg_name);
if (reg_off < 0)
return reg_off;
arg->reg_off = reg_off;
......
# TEMPORARY
# Alphabetical order
get_stack_raw_tp # spams with kernel warnings until next bpf -> bpf-next merge
stacktrace_build_id_nmi
stacktrace_build_id
stacktrace_build_id_nmi
task_fd_query_rawtp
varlen
# TEMPORARY
# Alphabetical order
atomics # attach(add): actual -524 <= expected 0 (trampoline)
bpf_iter_setsockopt # JIT does not support calling kernel function (kfunc)
bloom_filter_map # failed to find kernel BTF type ID of '__x64_sys_getpgid': -3 (?)
bpf_tcp_ca # JIT does not support calling kernel function (kfunc)
bpf_cookie # failed to open_and_load program: -524 (trampoline)
bpf_iter_setsockopt # JIT does not support calling kernel function (kfunc)
bpf_loop # attaches to __x64_sys_nanosleep
bpf_mod_race # BPF trampoline
bpf_nf # JIT does not support calling kernel function
bpf_tcp_ca # JIT does not support calling kernel function (kfunc)
cb_refs # expected error message unexpected error: -524 (trampoline)
cgroup_hierarchical_stats # JIT does not support calling kernel function (kfunc)
core_read_macros # unknown func bpf_probe_read#4 (overlapping)
d_path # failed to auto-attach program 'prog_stat': -524 (trampoline)
deny_namespace # failed to attach: ERROR: strerror_r(-524)=22 (trampoline)
dummy_st_ops # test_run unexpected error: -524 (errno 524) (trampoline)
fentry_fexit # fentry attach failed: -524 (trampoline)
fentry_test # fentry_first_attach unexpected error: -524 (trampoline)
......@@ -18,19 +23,28 @@ fexit_test # fexit_first_attach unexpected error:
get_func_args_test # trampoline
get_func_ip_test # get_func_ip_test__attach unexpected error: -524 (trampoline)
get_stack_raw_tp # user_stack corrupted user stack (no backchain userspace)
htab_update # failed to attach: ERROR: strerror_r(-524)=22 (trampoline)
kfree_skb # attach fentry unexpected error: -524 (trampoline)
kfunc_call # 'bpf_prog_active': not found in kernel BTF (?)
kfunc_dynptr_param # JIT does not support calling kernel function (kfunc)
kprobe_multi_test # relies on fentry
ksyms_module # test_ksyms_module__open_and_load unexpected error: -9 (?)
ksyms_module_libbpf # JIT does not support calling kernel function (kfunc)
ksyms_module_lskel # test_ksyms_module_lskel__open_and_load unexpected error: -9 (?)
libbpf_get_fd_by_id_opts # failed to attach: ERROR: strerror_r(-524)=22 (trampoline)
lookup_key # JIT does not support calling kernel function (kfunc)
lru_bug # prog 'printk': failed to auto-attach: -524
map_kptr # failed to open_and_load program: -524 (trampoline)
modify_return # modify_return attach failed: -524 (trampoline)
module_attach # skel_attach skeleton attach failed: -524 (trampoline)
mptcp
kprobe_multi_test # relies on fentry
netcnt # failed to load BPF skeleton 'netcnt_prog': -7 (?)
probe_user # check_kprobe_res wrong kprobe res from probe read (?)
recursion # skel_attach unexpected error: -524 (trampoline)
ringbuf # skel_load skeleton load failed (?)
select_reuseport # intermittently fails on new s390x setup
send_signal # intermittently fails to receive signal
setget_sockopt # attach unexpected error: -524 (trampoline)
sk_assign # Can't read on server: Invalid argument (?)
sk_lookup # endianness problem
sk_storage_tracing # test_sk_storage_tracing__attach unexpected error: -524 (trampoline)
......@@ -52,26 +66,15 @@ timer_mim # failed to auto-attach program 'test1'
trace_ext # failed to auto-attach program 'test_pkt_md_access_new': -524 (trampoline)
trace_printk # trace_printk__load unexpected error: -2 (errno 2) (?)
trace_vprintk # trace_vprintk__open_and_load unexpected error: -9 (?)
tracing_struct # failed to auto-attach: -524 (trampoline)
trampoline_count # prog 'prog1': failed to attach: ERROR: strerror_r(-524)=22 (trampoline)
unpriv_bpf_disabled # fentry
user_ringbuf # failed to find kernel BTF type ID of '__s390x_sys_prctl': -3 (?)
verif_stats # trace_vprintk__open_and_load unexpected error: -9 (?)
verify_pkcs7_sig # JIT does not support calling kernel function (kfunc)
vmlinux # failed to auto-attach program 'handle__fentry': -524 (trampoline)
xdp_adjust_tail # case-128 err 0 errno 28 retval 1 size 128 expect-size 3520 (?)
xdp_bonding # failed to auto-attach program 'trace_on_entry': -524 (trampoline)
xdp_bpf2bpf # failed to auto-attach program 'trace_on_entry': -524 (trampoline)
map_kptr # failed to open_and_load program: -524 (trampoline)
bpf_cookie # failed to open_and_load program: -524 (trampoline)
xdp_do_redirect # prog_run_max_size unexpected error: -22 (errno 22)
send_signal # intermittently fails to receive signal
select_reuseport # intermittently fails on new s390x setup
xdp_synproxy # JIT does not support calling kernel function (kfunc)
unpriv_bpf_disabled # fentry
lru_bug # prog 'printk': failed to auto-attach: -524
setget_sockopt # attach unexpected error: -524 (trampoline)
cb_refs # expected error message unexpected error: -524 (trampoline)
cgroup_hierarchical_stats # JIT does not support calling kernel function (kfunc)
htab_update # failed to attach: ERROR: strerror_r(-524)=22 (trampoline)
tracing_struct # failed to auto-attach: -524 (trampoline)
user_ringbuf # failed to find kernel BTF type ID of '__s390x_sys_prctl': -3 (?)
lookup_key # JIT does not support calling kernel function (kfunc)
verify_pkcs7_sig # JIT does not support calling kernel function (kfunc)
kfunc_dynptr_param # JIT does not support calling kernel function (kfunc)
......@@ -14,10 +14,11 @@ It's now possible to run the selftests using ``tools/testing/selftests/bpf/vmtes
The script tries to ensure that the tests are run with the same environment as they
would be run post-submit in the CI used by the Maintainers.
This script downloads a suitable Kconfig and VM userspace image from the system used by
the CI. It builds the kernel (without overwriting your existing Kconfig), recompiles the
bpf selftests, runs them (by default ``tools/testing/selftests/bpf/test_progs``) and
saves the resulting output (by default in ``~/.bpf_selftests``).
This script uses the in-tree kernel configuration and downloads a VM userspace
image from the system used by the CI. It builds the kernel (without overwriting
your existing Kconfig), recompiles the bpf selftests, runs them (by default
``tools/testing/selftests/bpf/test_progs``) and saves the resulting output (by
default in ``~/.bpf_selftests``).
Script dependencies:
- clang (preferably built from sources, https://github.com/llvm/llvm-project);
......@@ -26,7 +27,7 @@ Script dependencies:
- docutils (for ``rst2man``);
- libcap-devel.
For more information on about using the script, run:
For more information about using the script, run:
.. code-block:: console
......
......@@ -3,6 +3,7 @@
#include <test_progs.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <task_local_storage_helpers.h>
#include "bpf_iter_ipv6_route.skel.h"
#include "bpf_iter_netlink.skel.h"
#include "bpf_iter_bpf_map.skel.h"
......@@ -175,11 +176,6 @@ static void test_bpf_map(void)
bpf_iter_bpf_map__destroy(skel);
}
static int pidfd_open(pid_t pid, unsigned int flags)
{
return syscall(SYS_pidfd_open, pid, flags);
}
static void check_bpf_link_info(const struct bpf_program *prog)
{
LIBBPF_OPTS(bpf_iter_attach_opts, opts);
......@@ -295,8 +291,8 @@ static void test_task_pidfd(void)
union bpf_iter_link_info linfo;
int pidfd;
pidfd = pidfd_open(getpid(), 0);
if (!ASSERT_GT(pidfd, 0, "pidfd_open"))
pidfd = sys_pidfd_open(getpid(), 0);
if (!ASSERT_GT(pidfd, 0, "sys_pidfd_open"))
return;
memset(&linfo, 0, sizeof(linfo));
......@@ -1498,7 +1494,6 @@ static noinline int trigger_func(int arg)
static void test_task_vma_offset_common(struct bpf_iter_attach_opts *opts, bool one_proc)
{
struct bpf_iter_vma_offset *skel;
struct bpf_link *link;
char buf[16] = {};
int iter_fd, len;
int pgsz, shift;
......@@ -1513,11 +1508,11 @@ static void test_task_vma_offset_common(struct bpf_iter_attach_opts *opts, bool
;
skel->bss->page_shift = shift;
link = bpf_program__attach_iter(skel->progs.get_vma_offset, opts);
if (!ASSERT_OK_PTR(link, "attach_iter"))
return;
skel->links.get_vma_offset = bpf_program__attach_iter(skel->progs.get_vma_offset, opts);
if (!ASSERT_OK_PTR(skel->links.get_vma_offset, "attach_iter"))
goto exit;
iter_fd = bpf_iter_create(bpf_link__fd(link));
iter_fd = bpf_iter_create(bpf_link__fd(skel->links.get_vma_offset));
if (!ASSERT_GT(iter_fd, 0, "create_iter"))
goto exit;
......@@ -1535,7 +1530,7 @@ static void test_task_vma_offset_common(struct bpf_iter_attach_opts *opts, bool
close(iter_fd);
exit:
bpf_link__destroy(link);
bpf_iter_vma_offset__destroy(skel);
}
static void test_task_vma_offset(void)
......
......@@ -49,14 +49,14 @@ static int connect_to_server(int srv_fd)
static void test_bpf_nf_ct(int mode)
{
const char *iptables = "iptables -t raw %s PREROUTING -j CONNMARK --set-mark 42/0";
const char *iptables = "iptables-legacy -t raw %s PREROUTING -j CONNMARK --set-mark 42/0";
int srv_fd = -1, client_fd = -1, srv_client_fd = -1;
struct sockaddr_in peer_addr = {};
struct test_bpf_nf *skel;
int prog_fd, err;
socklen_t len;
u16 srv_port;
char cmd[64];
char cmd[128];
LIBBPF_OPTS(bpf_test_run_opts, topts,
.data_in = &pkt_v4,
.data_size_in = sizeof(pkt_v4),
......@@ -69,7 +69,7 @@ static void test_bpf_nf_ct(int mode)
/* Enable connection tracking */
snprintf(cmd, sizeof(cmd), iptables, "-A");
if (!ASSERT_OK(system(cmd), "iptables"))
if (!ASSERT_OK(system(cmd), cmd))
goto end;
srv_port = (mode == TEST_XDP) ? 5005 : 5006;
......
......@@ -325,7 +325,7 @@ static bool symbol_equal(const void *key1, const void *key2, void *ctx __maybe_u
static int get_syms(char ***symsp, size_t *cntp)
{
size_t cap = 0, cnt = 0, i;
char *name, **syms = NULL;
char *name = NULL, **syms = NULL;
struct hashmap *map;
char buf[256];
FILE *f;
......@@ -352,6 +352,8 @@ static int get_syms(char ***symsp, size_t *cntp)
/* skip modules */
if (strchr(buf, '['))
continue;
free(name);
if (sscanf(buf, "%ms$*[^\n]\n", &name) != 1)
continue;
/*
......@@ -369,32 +371,32 @@ static int get_syms(char ***symsp, size_t *cntp)
if (!strncmp(name, "__ftrace_invalid_address__",
sizeof("__ftrace_invalid_address__") - 1))
continue;
err = hashmap__add(map, name, NULL);
if (err) {
free(name);
if (err == -EEXIST)
continue;
if (err == -EEXIST)
continue;
if (err)
goto error;
}
err = libbpf_ensure_mem((void **) &syms, &cap,
sizeof(*syms), cnt + 1);
if (err) {
free(name);
if (err)
goto error;
}
syms[cnt] = name;
cnt++;
syms[cnt++] = name;
name = NULL;
}
*symsp = syms;
*cntp = cnt;
error:
free(name);
fclose(f);
hashmap__free(map);
if (err) {
for (i = 0; i < cnt; i++)
free(syms[cnt]);
free(syms[i]);
free(syms);
}
return err;
......
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2022 Huawei Technologies Duesseldorf GmbH
*
* Author: Roberto Sassu <roberto.sassu@huawei.com>
*/
#include <test_progs.h>
#include "test_libbpf_get_fd_by_id_opts.skel.h"
void test_libbpf_get_fd_by_id_opts(void)
{
struct test_libbpf_get_fd_by_id_opts *skel;
struct bpf_map_info info_m = {};
__u32 len = sizeof(info_m), value;
int ret, zero = 0, fd = -1;
LIBBPF_OPTS(bpf_get_fd_by_id_opts, fd_opts_rdonly,
.open_flags = BPF_F_RDONLY,
);
skel = test_libbpf_get_fd_by_id_opts__open_and_load();
if (!ASSERT_OK_PTR(skel,
"test_libbpf_get_fd_by_id_opts__open_and_load"))
return;
ret = test_libbpf_get_fd_by_id_opts__attach(skel);
if (!ASSERT_OK(ret, "test_libbpf_get_fd_by_id_opts__attach"))
goto close_prog;
ret = bpf_obj_get_info_by_fd(bpf_map__fd(skel->maps.data_input),
&info_m, &len);
if (!ASSERT_OK(ret, "bpf_obj_get_info_by_fd"))
goto close_prog;
fd = bpf_map_get_fd_by_id(info_m.id);
if (!ASSERT_LT(fd, 0, "bpf_map_get_fd_by_id"))
goto close_prog;
fd = bpf_map_get_fd_by_id_opts(info_m.id, NULL);
if (!ASSERT_LT(fd, 0, "bpf_map_get_fd_by_id_opts"))
goto close_prog;
fd = bpf_map_get_fd_by_id_opts(info_m.id, &fd_opts_rdonly);
if (!ASSERT_GE(fd, 0, "bpf_map_get_fd_by_id_opts"))
goto close_prog;
/* Map lookup should work with read-only fd. */
ret = bpf_map_lookup_elem(fd, &zero, &value);
if (!ASSERT_OK(ret, "bpf_map_lookup_elem"))
goto close_prog;
if (!ASSERT_EQ(value, 0, "map value mismatch"))
goto close_prog;
/* Map update should not work with read-only fd. */
ret = bpf_map_update_elem(fd, &zero, &len, BPF_ANY);
if (!ASSERT_LT(ret, 0, "bpf_map_update_elem"))
goto close_prog;
/* Map update should work with read-write fd. */
ret = bpf_map_update_elem(bpf_map__fd(skel->maps.data_input), &zero,
&len, BPF_ANY);
if (!ASSERT_OK(ret, "bpf_map_update_elem"))
goto close_prog;
/* Prog get fd with opts set should not work (no kernel support). */
ret = bpf_prog_get_fd_by_id_opts(0, &fd_opts_rdonly);
if (!ASSERT_EQ(ret, -EINVAL, "bpf_prog_get_fd_by_id_opts"))
goto close_prog;
/* Link get fd with opts set should not work (no kernel support). */
ret = bpf_link_get_fd_by_id_opts(0, &fd_opts_rdonly);
if (!ASSERT_EQ(ret, -EINVAL, "bpf_link_get_fd_by_id_opts"))
goto close_prog;
/* BTF get fd with opts set should not work (no kernel support). */
ret = bpf_btf_get_fd_by_id_opts(0, &fd_opts_rdonly);
ASSERT_EQ(ret, -EINVAL, "bpf_btf_get_fd_by_id_opts");
close_prog:
if (fd >= 0)
close(fd);
test_libbpf_get_fd_by_id_opts__destroy(skel);
}
......@@ -105,7 +105,7 @@ static void test_map_kptr_success(bool test_run)
ASSERT_OK(opts.retval, "test_map_kptr_ref2 retval");
if (test_run)
return;
goto exit;
ret = bpf_map__update_elem(skel->maps.array_map,
&key, sizeof(key), buf, sizeof(buf), 0);
......@@ -132,6 +132,7 @@ static void test_map_kptr_success(bool test_run)
ret = bpf_map__delete_elem(skel->maps.lru_hash_map, &key, sizeof(key), 0);
ASSERT_OK(ret, "lru_hash_map delete");
exit:
map_kptr__destroy(skel);
}
......
......@@ -15,7 +15,7 @@ static void test_fentry(void)
err = tracing_struct__attach(skel);
if (!ASSERT_OK(err, "tracing_struct__attach"))
return;
goto destroy_skel;
ASSERT_OK(trigger_module_test_read(256), "trigger_read");
......@@ -54,6 +54,7 @@ static void test_fentry(void)
ASSERT_EQ(skel->bss->t5_ret, 1, "t5 ret");
tracing_struct__detach(skel);
destroy_skel:
tracing_struct__destroy(skel);
}
......
......@@ -18,7 +18,7 @@ static void test_xdp_adjust_tail_shrink(void)
);
err = bpf_prog_test_load(file, BPF_PROG_TYPE_XDP, &obj, &prog_fd);
if (ASSERT_OK(err, "test_xdp_adjust_tail_shrink"))
if (!ASSERT_OK(err, "test_xdp_adjust_tail_shrink"))
return;
err = bpf_prog_test_run_opts(prog_fd, &topts);
......@@ -53,7 +53,7 @@ static void test_xdp_adjust_tail_grow(void)
);
err = bpf_prog_test_load(file, BPF_PROG_TYPE_XDP, &obj, &prog_fd);
if (ASSERT_OK(err, "test_xdp_adjust_tail_grow"))
if (!ASSERT_OK(err, "test_xdp_adjust_tail_grow"))
return;
err = bpf_prog_test_run_opts(prog_fd, &topts);
......@@ -63,6 +63,7 @@ static void test_xdp_adjust_tail_grow(void)
expect_sz = sizeof(pkt_v6) + 40; /* Test grow with 40 bytes */
topts.data_in = &pkt_v6;
topts.data_size_in = sizeof(pkt_v6);
topts.data_size_out = sizeof(buf);
err = bpf_prog_test_run_opts(prog_fd, &topts);
ASSERT_OK(err, "ipv6");
ASSERT_EQ(topts.retval, XDP_TX, "ipv6 retval");
......@@ -89,7 +90,7 @@ static void test_xdp_adjust_tail_grow2(void)
);
err = bpf_prog_test_load(file, BPF_PROG_TYPE_XDP, &obj, &prog_fd);
if (ASSERT_OK(err, "test_xdp_adjust_tail_grow"))
if (!ASSERT_OK(err, "test_xdp_adjust_tail_grow"))
return;
/* Test case-64 */
......
......@@ -94,12 +94,12 @@ static void test_synproxy(bool xdp)
SYS("sysctl -w net.ipv4.tcp_syncookies=2");
SYS("sysctl -w net.ipv4.tcp_timestamps=1");
SYS("sysctl -w net.netfilter.nf_conntrack_tcp_loose=0");
SYS("iptables -t raw -I PREROUTING \
SYS("iptables-legacy -t raw -I PREROUTING \
-i tmp1 -p tcp -m tcp --syn --dport 8080 -j CT --notrack");
SYS("iptables -t filter -A INPUT \
SYS("iptables-legacy -t filter -A INPUT \
-i tmp1 -p tcp -m tcp --dport 8080 -m state --state INVALID,UNTRACKED \
-j SYNPROXY --sack-perm --timestamp --wscale 7 --mss 1460");
SYS("iptables -t filter -A INPUT \
SYS("iptables-legacy -t filter -A INPUT \
-i tmp1 -m state --state INVALID -j DROP");
ctrl_file = SYS_OUT("./xdp_synproxy --iface tmp1 --ports 8080 \
......
......@@ -102,12 +102,21 @@ struct zone {
struct zone_padding __pad__;
};
/* ----- START-EXPECTED-OUTPUT ----- */
struct padding_wo_named_members {
long: 64;
long: 64;
};
/* ------ END-EXPECTED-OUTPUT ------ */
int f(struct {
struct padded_implicitly _1;
struct padded_explicitly _2;
struct padded_a_lot _3;
struct padded_cache_line _4;
struct zone _5;
struct padding_wo_named_members _6;
} *_)
{
return 0;
......
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2022 Huawei Technologies Duesseldorf GmbH
*
* Author: Roberto Sassu <roberto.sassu@huawei.com>
*/
#include "vmlinux.h"
#include <errno.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
/* From include/linux/mm.h. */
#define FMODE_WRITE 0x2
struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
__uint(max_entries, 1);
__type(key, __u32);
__type(value, __u32);
} data_input SEC(".maps");
char _license[] SEC("license") = "GPL";
SEC("lsm/bpf_map")
int BPF_PROG(check_access, struct bpf_map *map, fmode_t fmode)
{
if (map != (struct bpf_map *)&data_input)
return 0;
if (fmode & FMODE_WRITE)
return -EACCES;
return 0;
}
......@@ -7,8 +7,12 @@
#include <sys/types.h>
#ifndef __NR_pidfd_open
#ifdef __alpha__
#define __NR_pidfd_open 544
#else
#define __NR_pidfd_open 434
#endif
#endif
static inline int sys_pidfd_open(pid_t pid, unsigned int flags)
{
......
......@@ -68,7 +68,6 @@
#define SKIP_INSNS() BPF_RAW_INSN(0xde, 0xa, 0xd, 0xbeef, 0xdeadbeef)
#define DEFAULT_LIBBPF_LOG_LEVEL 4
#define VERBOSE_LIBBPF_LOG_LEVEL 1
#define F_NEEDS_EFFICIENT_UNALIGNED_ACCESS (1 << 0)
#define F_LOAD_WITH_STRICT_ALIGNMENT (1 << 1)
......@@ -81,6 +80,7 @@
static bool unpriv_disabled = false;
static int skips;
static bool verbose = false;
static int verif_log_level = 0;
struct kfunc_btf_id_pair {
const char *kfunc;
......@@ -759,7 +759,7 @@ static int load_btf_spec(__u32 *types, int types_len,
.log_buf = bpf_vlog,
.log_size = sizeof(bpf_vlog),
.log_level = (verbose
? VERBOSE_LIBBPF_LOG_LEVEL
? verif_log_level
: DEFAULT_LIBBPF_LOG_LEVEL),
);
......@@ -1491,7 +1491,7 @@ static void do_test_single(struct bpf_test *test, bool unpriv,
opts.expected_attach_type = test->expected_attach_type;
if (verbose)
opts.log_level = VERBOSE_LIBBPF_LOG_LEVEL;
opts.log_level = verif_log_level | 4; /* force stats */
else if (expected_ret == VERBOSE_ACCEPT)
opts.log_level = 2;
else
......@@ -1746,6 +1746,13 @@ int main(int argc, char **argv)
if (argc > 1 && strcmp(argv[1], "-v") == 0) {
arg++;
verbose = true;
verif_log_level = 1;
argc--;
}
if (argc > 1 && strcmp(argv[1], "-vv") == 0) {
arg++;
verbose = true;
verif_log_level = 2;
argc--;
}
......
......@@ -509,6 +509,28 @@ static int parse_verif_log(char * const buf, size_t buf_sz, struct verif_stats *
return 0;
}
static void fixup_obj(struct bpf_object *obj)
{
struct bpf_map *map;
bpf_object__for_each_map(map, obj) {
/* disable pinning */
bpf_map__set_pin_path(map, NULL);
/* fix up map size, if necessary */
switch (bpf_map__type(map)) {
case BPF_MAP_TYPE_SK_STORAGE:
case BPF_MAP_TYPE_TASK_STORAGE:
case BPF_MAP_TYPE_INODE_STORAGE:
case BPF_MAP_TYPE_CGROUP_STORAGE:
break;
default:
if (bpf_map__max_entries(map) == 0)
bpf_map__set_max_entries(map, 1);
}
}
}
static int process_prog(const char *filename, struct bpf_object *obj, struct bpf_program *prog)
{
const char *prog_name = bpf_program__name(prog);
......@@ -543,6 +565,9 @@ static int process_prog(const char *filename, struct bpf_object *obj, struct bpf
}
verif_log_buf[0] = '\0';
/* increase chances of successful BPF object loading */
fixup_obj(obj);
err = bpf_object__load(obj);
env.progs_processed++;
......@@ -1104,17 +1129,21 @@ static void output_comp_stats(const struct verif_stats *base, const struct verif
else
snprintf(diff_buf, sizeof(diff_buf), "%s", "MISMATCH");
} else {
double p = 0.0;
snprintf(base_buf, sizeof(base_buf), "%ld", base_val);
snprintf(comp_buf, sizeof(comp_buf), "%ld", comp_val);
diff_val = comp_val - base_val;
if (base == &fallback_stats || comp == &fallback_stats || base_val == 0) {
snprintf(diff_buf, sizeof(diff_buf), "%+ld (%+.2lf%%)",
diff_val, comp_val < base_val ? -100.0 : 100.0);
if (comp_val == base_val)
p = 0.0; /* avoid +0 (+100%) case */
else
p = comp_val < base_val ? -100.0 : 100.0;
} else {
snprintf(diff_buf, sizeof(diff_buf), "%+ld (%+.2lf%%)",
diff_val, diff_val * 100.0 / base_val);
p = diff_val * 100.0 / base_val;
}
snprintf(diff_buf, sizeof(diff_buf), "%+ld (%+.2lf%%)", diff_val, p);
}
switch (fmt) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment