mirror of https://gitee.com/openkylin/linux.git
libbpf: Allow to augment system Kconfig through extra optional config
Instead of all or nothing approach of overriding Kconfig file location, allow to extend it with extra values and override chosen subset of values though optional user-provided extra config, passed as a string through open options' .kconfig option. If same config key is present in both user-supplied config and Kconfig, user-supplied one wins. This allows applications to more easily test various conditions despite host kernel's real configuration. If all of BPF object's __kconfig externs are satisfied from user-supplied config, system Kconfig won't be read at all. Simplify selftests by not needing to create temporary Kconfig files. Suggested-by: Alexei Starovoitov <ast@fb.com> Signed-off-by: Andrii Nakryiko <andriin@fb.com> Signed-off-by: Alexei Starovoitov <ast@kernel.org> Link: https://lore.kernel.org/bpf/20191219002837.3074619-3-andriin@fb.com
This commit is contained in:
parent
81bfdd087b
commit
8601fd4221
|
@ -302,7 +302,7 @@ struct bpf_object {
|
|||
size_t nr_maps;
|
||||
size_t maps_cap;
|
||||
|
||||
char *kconfig_path;
|
||||
char *kconfig;
|
||||
struct extern_desc *externs;
|
||||
int nr_extern;
|
||||
int kconfig_map_idx;
|
||||
|
@ -1149,94 +1149,98 @@ static int set_ext_value_num(struct extern_desc *ext, void *ext_val,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int bpf_object__read_kernel_config(struct bpf_object *obj,
|
||||
const char *config_path,
|
||||
void *data)
|
||||
static int bpf_object__process_kconfig_line(struct bpf_object *obj,
|
||||
char *buf, void *data)
|
||||
{
|
||||
char buf[PATH_MAX], *sep, *value;
|
||||
struct extern_desc *ext;
|
||||
char *sep, *value;
|
||||
int len, err = 0;
|
||||
void *ext_val;
|
||||
__u64 num;
|
||||
|
||||
if (strncmp(buf, "CONFIG_", 7))
|
||||
return 0;
|
||||
|
||||
sep = strchr(buf, '=');
|
||||
if (!sep) {
|
||||
pr_warn("failed to parse '%s': no separator\n", buf);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Trim ending '\n' */
|
||||
len = strlen(buf);
|
||||
if (buf[len - 1] == '\n')
|
||||
buf[len - 1] = '\0';
|
||||
/* Split on '=' and ensure that a value is present. */
|
||||
*sep = '\0';
|
||||
if (!sep[1]) {
|
||||
*sep = '=';
|
||||
pr_warn("failed to parse '%s': no value\n", buf);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ext = find_extern_by_name(obj, buf);
|
||||
if (!ext || ext->is_set)
|
||||
return 0;
|
||||
|
||||
ext_val = data + ext->data_off;
|
||||
value = sep + 1;
|
||||
|
||||
switch (*value) {
|
||||
case 'y': case 'n': case 'm':
|
||||
err = set_ext_value_tri(ext, ext_val, *value);
|
||||
break;
|
||||
case '"':
|
||||
err = set_ext_value_str(ext, ext_val, value);
|
||||
break;
|
||||
default:
|
||||
/* assume integer */
|
||||
err = parse_u64(value, &num);
|
||||
if (err) {
|
||||
pr_warn("extern %s=%s should be integer\n",
|
||||
ext->name, value);
|
||||
return err;
|
||||
}
|
||||
err = set_ext_value_num(ext, ext_val, num);
|
||||
break;
|
||||
}
|
||||
if (err)
|
||||
return err;
|
||||
pr_debug("extern %s=%s\n", ext->name, value);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bpf_object__read_kconfig_file(struct bpf_object *obj, void *data)
|
||||
{
|
||||
char buf[PATH_MAX];
|
||||
struct utsname uts;
|
||||
int len, err = 0;
|
||||
gzFile file;
|
||||
|
||||
if (config_path) {
|
||||
file = gzopen(config_path, "r");
|
||||
} else {
|
||||
struct utsname uts;
|
||||
uname(&uts);
|
||||
len = snprintf(buf, PATH_MAX, "/boot/config-%s", uts.release);
|
||||
if (len < 0)
|
||||
return -EINVAL;
|
||||
else if (len >= PATH_MAX)
|
||||
return -ENAMETOOLONG;
|
||||
|
||||
/* gzopen also accepts uncompressed files. */
|
||||
file = gzopen(buf, "r");
|
||||
if (!file)
|
||||
file = gzopen("/proc/config.gz", "r");
|
||||
|
||||
uname(&uts);
|
||||
len = snprintf(buf, PATH_MAX, "/boot/config-%s", uts.release);
|
||||
if (len < 0)
|
||||
return -EINVAL;
|
||||
else if (len >= PATH_MAX)
|
||||
return -ENAMETOOLONG;
|
||||
/* gzopen also accepts uncompressed files. */
|
||||
file = gzopen(buf, "r");
|
||||
if (!file)
|
||||
file = gzopen("/proc/config.gz", "r");
|
||||
}
|
||||
if (!file) {
|
||||
pr_warn("failed to read kernel config at '%s'\n", config_path);
|
||||
pr_warn("failed to open system Kconfig\n");
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
while (gzgets(file, buf, sizeof(buf))) {
|
||||
if (strncmp(buf, "CONFIG_", 7))
|
||||
continue;
|
||||
|
||||
sep = strchr(buf, '=');
|
||||
if (!sep) {
|
||||
err = -EINVAL;
|
||||
pr_warn("failed to parse '%s': no separator\n", buf);
|
||||
err = bpf_object__process_kconfig_line(obj, buf, data);
|
||||
if (err) {
|
||||
pr_warn("error parsing system Kconfig line '%s': %d\n",
|
||||
buf, err);
|
||||
goto out;
|
||||
}
|
||||
/* Trim ending '\n' */
|
||||
len = strlen(buf);
|
||||
if (buf[len - 1] == '\n')
|
||||
buf[len - 1] = '\0';
|
||||
/* Split on '=' and ensure that a value is present. */
|
||||
*sep = '\0';
|
||||
if (!sep[1]) {
|
||||
err = -EINVAL;
|
||||
*sep = '=';
|
||||
pr_warn("failed to parse '%s': no value\n", buf);
|
||||
goto out;
|
||||
}
|
||||
|
||||
ext = find_extern_by_name(obj, buf);
|
||||
if (!ext)
|
||||
continue;
|
||||
if (ext->is_set) {
|
||||
err = -EINVAL;
|
||||
pr_warn("re-defining extern '%s' not allowed\n", buf);
|
||||
goto out;
|
||||
}
|
||||
|
||||
ext_val = data + ext->data_off;
|
||||
value = sep + 1;
|
||||
|
||||
switch (*value) {
|
||||
case 'y': case 'n': case 'm':
|
||||
err = set_ext_value_tri(ext, ext_val, *value);
|
||||
break;
|
||||
case '"':
|
||||
err = set_ext_value_str(ext, ext_val, value);
|
||||
break;
|
||||
default:
|
||||
/* assume integer */
|
||||
err = parse_u64(value, &num);
|
||||
if (err) {
|
||||
pr_warn("extern %s=%s should be integer\n",
|
||||
ext->name, value);
|
||||
goto out;
|
||||
}
|
||||
err = set_ext_value_num(ext, ext_val, num);
|
||||
break;
|
||||
}
|
||||
if (err)
|
||||
goto out;
|
||||
pr_debug("extern %s=%s\n", ext->name, value);
|
||||
}
|
||||
|
||||
out:
|
||||
|
@ -1244,6 +1248,33 @@ static int bpf_object__read_kernel_config(struct bpf_object *obj,
|
|||
return err;
|
||||
}
|
||||
|
||||
static int bpf_object__read_kconfig_mem(struct bpf_object *obj,
|
||||
const char *config, void *data)
|
||||
{
|
||||
char buf[PATH_MAX];
|
||||
int err = 0;
|
||||
FILE *file;
|
||||
|
||||
file = fmemopen((void *)config, strlen(config), "r");
|
||||
if (!file) {
|
||||
err = -errno;
|
||||
pr_warn("failed to open in-memory Kconfig: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
while (fgets(buf, sizeof(buf), file)) {
|
||||
err = bpf_object__process_kconfig_line(obj, buf, data);
|
||||
if (err) {
|
||||
pr_warn("error parsing in-memory Kconfig line '%s': %d\n",
|
||||
buf, err);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
fclose(file);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int bpf_object__init_kconfig_map(struct bpf_object *obj)
|
||||
{
|
||||
struct extern_desc *last_ext;
|
||||
|
@ -4567,7 +4598,7 @@ static struct bpf_object *
|
|||
__bpf_object__open(const char *path, const void *obj_buf, size_t obj_buf_sz,
|
||||
const struct bpf_object_open_opts *opts)
|
||||
{
|
||||
const char *obj_name, *kconfig_path;
|
||||
const char *obj_name, *kconfig;
|
||||
struct bpf_program *prog;
|
||||
struct bpf_object *obj;
|
||||
char tmp_name[64];
|
||||
|
@ -4599,10 +4630,10 @@ __bpf_object__open(const char *path, const void *obj_buf, size_t obj_buf_sz,
|
|||
return obj;
|
||||
|
||||
obj->relaxed_core_relocs = OPTS_GET(opts, relaxed_core_relocs, false);
|
||||
kconfig_path = OPTS_GET(opts, kconfig_path, NULL);
|
||||
if (kconfig_path) {
|
||||
obj->kconfig_path = strdup(kconfig_path);
|
||||
if (!obj->kconfig_path)
|
||||
kconfig = OPTS_GET(opts, kconfig, NULL);
|
||||
if (kconfig) {
|
||||
obj->kconfig = strdup(kconfig);
|
||||
if (!obj->kconfig)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
}
|
||||
|
||||
|
@ -4745,7 +4776,7 @@ static int bpf_object__sanitize_maps(struct bpf_object *obj)
|
|||
}
|
||||
|
||||
static int bpf_object__resolve_externs(struct bpf_object *obj,
|
||||
const char *config_path)
|
||||
const char *extra_kconfig)
|
||||
{
|
||||
bool need_config = false;
|
||||
struct extern_desc *ext;
|
||||
|
@ -4779,8 +4810,21 @@ static int bpf_object__resolve_externs(struct bpf_object *obj,
|
|||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
if (need_config && extra_kconfig) {
|
||||
err = bpf_object__read_kconfig_mem(obj, extra_kconfig, data);
|
||||
if (err)
|
||||
return -EINVAL;
|
||||
need_config = false;
|
||||
for (i = 0; i < obj->nr_extern; i++) {
|
||||
ext = &obj->externs[i];
|
||||
if (!ext->is_set) {
|
||||
need_config = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (need_config) {
|
||||
err = bpf_object__read_kernel_config(obj, config_path, data);
|
||||
err = bpf_object__read_kconfig_file(obj, data);
|
||||
if (err)
|
||||
return -EINVAL;
|
||||
}
|
||||
|
@ -4818,7 +4862,7 @@ int bpf_object__load_xattr(struct bpf_object_load_attr *attr)
|
|||
obj->loaded = true;
|
||||
|
||||
err = bpf_object__probe_caps(obj);
|
||||
err = err ? : bpf_object__resolve_externs(obj, obj->kconfig_path);
|
||||
err = err ? : bpf_object__resolve_externs(obj, obj->kconfig);
|
||||
err = err ? : bpf_object__sanitize_and_load_btf(obj);
|
||||
err = err ? : bpf_object__sanitize_maps(obj);
|
||||
err = err ? : bpf_object__create_maps(obj);
|
||||
|
@ -5412,7 +5456,7 @@ void bpf_object__close(struct bpf_object *obj)
|
|||
zfree(&map->pin_path);
|
||||
}
|
||||
|
||||
zfree(&obj->kconfig_path);
|
||||
zfree(&obj->kconfig);
|
||||
zfree(&obj->externs);
|
||||
obj->nr_extern = 0;
|
||||
|
||||
|
|
|
@ -85,12 +85,12 @@ struct bpf_object_open_opts {
|
|||
*/
|
||||
const char *pin_root_path;
|
||||
__u32 attach_prog_fd;
|
||||
/* kernel config file path override (for CONFIG_ externs); can point
|
||||
* to either uncompressed text file or .gz file
|
||||
/* Additional kernel config content that augments and overrides
|
||||
* system Kconfig for CONFIG_xxx externs.
|
||||
*/
|
||||
const char *kconfig_path;
|
||||
const char *kconfig;
|
||||
};
|
||||
#define bpf_object_open_opts__last_field kconfig_path
|
||||
#define bpf_object_open_opts__last_field kconfig
|
||||
|
||||
LIBBPF_API struct bpf_object *bpf_object__open(const char *path);
|
||||
LIBBPF_API struct bpf_object *
|
||||
|
|
|
@ -23,19 +23,13 @@ static uint32_t get_kernel_version(void)
|
|||
static struct test_case {
|
||||
const char *name;
|
||||
const char *cfg;
|
||||
const char *cfg_path;
|
||||
bool fails;
|
||||
struct test_core_extern__data data;
|
||||
} test_cases[] = {
|
||||
{ .name = "default search path", .cfg_path = NULL,
|
||||
.data = { .bpf_syscall = true } },
|
||||
{ .name = "/proc/config.gz", .cfg_path = "/proc/config.gz",
|
||||
.data = { .bpf_syscall = true } },
|
||||
{ .name = "missing config", .fails = true,
|
||||
.cfg_path = "/proc/invalid-config.gz" },
|
||||
{ .name = "default search path", .data = { .bpf_syscall = true } },
|
||||
{
|
||||
.name = "custom values",
|
||||
.cfg = "CONFIG_BPF_SYSCALL=y\n"
|
||||
.cfg = "CONFIG_BPF_SYSCALL=n\n"
|
||||
"CONFIG_TRISTATE=m\n"
|
||||
"CONFIG_BOOL=y\n"
|
||||
"CONFIG_CHAR=100\n"
|
||||
|
@ -45,7 +39,7 @@ static struct test_case {
|
|||
"CONFIG_STR=\"abracad\"\n"
|
||||
"CONFIG_MISSING=0",
|
||||
.data = {
|
||||
.bpf_syscall = true,
|
||||
.bpf_syscall = false,
|
||||
.tristate_val = TRI_MODULE,
|
||||
.bool_val = true,
|
||||
.char_val = 100,
|
||||
|
@ -133,30 +127,14 @@ void test_core_extern(void)
|
|||
int n = sizeof(*skel->data) / sizeof(uint64_t);
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(test_cases); i++) {
|
||||
char tmp_cfg_path[] = "/tmp/test_core_extern_cfg.XXXXXX";
|
||||
struct test_case *t = &test_cases[i];
|
||||
DECLARE_LIBBPF_OPTS(bpf_object_open_opts, opts,
|
||||
.kconfig_path = t->cfg_path,
|
||||
.kconfig = t->cfg,
|
||||
);
|
||||
|
||||
if (!test__start_subtest(t->name))
|
||||
continue;
|
||||
|
||||
if (t->cfg) {
|
||||
size_t n = strlen(t->cfg) + 1;
|
||||
int fd = mkstemp(tmp_cfg_path);
|
||||
int written;
|
||||
|
||||
if (CHECK(fd < 0, "mkstemp", "errno: %d\n", errno))
|
||||
continue;
|
||||
printf("using '%s' as config file\n", tmp_cfg_path);
|
||||
written = write(fd, t->cfg, n);
|
||||
close(fd);
|
||||
if (CHECK_FAIL(written != n))
|
||||
goto cleanup;
|
||||
opts.kconfig_path = tmp_cfg_path;
|
||||
}
|
||||
|
||||
skel = test_core_extern__open_opts(&opts);
|
||||
if (CHECK(!skel, "skel_open", "skeleton open failed\n"))
|
||||
goto cleanup;
|
||||
|
@ -185,8 +163,6 @@ void test_core_extern(void)
|
|||
j, exp[j], got[j]);
|
||||
}
|
||||
cleanup:
|
||||
if (t->cfg)
|
||||
unlink(tmp_cfg_path);
|
||||
test_core_extern__destroy(skel);
|
||||
skel = NULL;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue