Blob Blame History Raw
From: Andrii Nakryiko <andriin@fb.com>
Date: Wed, 18 Dec 2019 16:28:35 -0800
Subject: libbpf: Allow to augment system Kconfig through extra optional config
Patch-mainline: v5.6-rc1
Git-commit: 8601fd422148a8f7ff5f7eaf75b6703d5166332c
References: bsc#1177028

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
Acked-by: Gary Lin <glin@suse.com>
---
 tools/lib/bpf/libbpf.c                               |  204 +++++++++++--------
 tools/lib/bpf/libbpf.h                               |    8 
 tools/testing/selftests/bpf/prog_tests/core_extern.c |   32 --
 3 files changed, 132 insertions(+), 112 deletions(-)

--- a/tools/lib/bpf/libbpf.c
+++ b/tools/lib/bpf/libbpf.c
@@ -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,98 +1149,129 @@ static int set_ext_value_num(struct exte
 	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;
-	gzFile file;
 
-	if (config_path) {
-		file = gzopen(config_path, "r");
-	} else {
-		struct utsname uts;
+	if (strncmp(buf, "CONFIG_", 7))
+		return 0;
 
-		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");
+	sep = strchr(buf, '=');
+	if (!sep) {
+		pr_warning("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_warning("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_warning("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;
+
+	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_warning("failed to read kernel config at '%s'\n", config_path);
+		pr_warning("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_warning("failed to parse '%s': no separator\n", buf);
-			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_warning("failed to parse '%s': no value\n", buf);
+		err = bpf_object__process_kconfig_line(obj, buf, data);
+		if (err) {
+			pr_warning("error parsing system Kconfig line '%s': %d\n",
+				   buf, err);
 			goto out;
 		}
+	}
 
-		ext = find_extern_by_name(obj, buf);
-		if (!ext)
-			continue;
-		if (ext->is_set) {
-			err = -EINVAL;
-			pr_warning("re-defining extern '%s' not allowed\n", buf);
-			goto out;
-		}
+out:
+	gzclose(file);
+	return err;
+}
 
-		ext_val = data + ext->data_off;
-		value = sep + 1;
+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;
 
-		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_warning("extern %s=%s should be integer\n",
-					ext->name, value);
-				goto out;
-			}
-			err = set_ext_value_num(ext, ext_val, num);
+	file = fmemopen((void *)config, strlen(config), "r");
+	if (!file) {
+		err = -errno;
+		pr_warning("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_warning("error parsing in-memory Kconfig line '%s': %d\n",
+				   buf, err);
 			break;
 		}
-		if (err)
-			goto out;
-		pr_debug("extern %s=%s\n", ext->name, value);
 	}
 
-out:
-	gzclose(file);
+	fclose(file);
 	return err;
 }
 
@@ -4570,7 +4601,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];
@@ -4602,10 +4633,10 @@ __bpf_object__open(const char *path, con
 		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);
 	}
 
@@ -4748,7 +4779,7 @@ static int bpf_object__sanitize_maps(str
 }
 
 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;
@@ -4782,8 +4813,21 @@ static int bpf_object__resolve_externs(s
 			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;
 	}
@@ -4821,7 +4865,7 @@ int bpf_object__load_xattr(struct bpf_ob
 	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);
@@ -5415,7 +5459,7 @@ void bpf_object__close(struct bpf_object
 		zfree(&map->pin_path);
 	}
 
-	zfree(&obj->kconfig_path);
+	zfree(&obj->kconfig);
 	zfree(&obj->externs);
 	obj->nr_extern = 0;
 
--- a/tools/lib/bpf/libbpf.h
+++ b/tools/lib/bpf/libbpf.h
@@ -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 *
--- a/tools/testing/selftests/bpf/prog_tests/core_extern.c
+++ b/tools/testing/selftests/bpf/prog_tests/core_extern.c
@@ -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;
 	}