forked from Minki/linux
49eb7ab3b2
Add a new component and command for bpftool, in order to probe the system to dump a set of eBPF-related parameters so that users can know what features are available on the system. Parameters are dumped in plain or JSON output (with -j/-p options). The current patch introduces probing of one simple parameter: availability of the bpf() system call. Later commits will add other probes. Sample output: # bpftool feature probe kernel Scanning system call availability... bpf() syscall is available # bpftool --json --pretty feature probe kernel { "syscall_config": { "have_bpf_syscall": true } } The optional "kernel" keyword enforces probing of the current system, which is the only possible behaviour at this stage. It can be safely omitted. The feature comes with the relevant man page, but bash completion will come in a dedicated commit. v3: - Do not probe kernel version. Contrarily to what is written below for v2, we can have the kernel version retrieved in libbpf instead of bpftool (in the patch adding probing for program types). v2: - Remove C-style macros output from this patch. - Even though kernel version is no longer needed for testing kprobes availability, note that we still collect it in this patch so that bpftool gets able to probe (in next patches) older kernels as well. Signed-off-by: Quentin Monnet <quentin.monnet@netronome.com> Reviewed-by: Jakub Kicinski <jakub.kicinski@netronome.com> Reviewed-by: Stanislav Fomichev <sdf@google.com> Signed-off-by: Alexei Starovoitov <ast@kernel.org>
390 lines
7.4 KiB
C
390 lines
7.4 KiB
C
// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
|
/* Copyright (C) 2017-2018 Netronome Systems, Inc. */
|
|
|
|
#include <ctype.h>
|
|
#include <errno.h>
|
|
#include <getopt.h>
|
|
#include <linux/bpf.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include <bpf.h>
|
|
|
|
#include "main.h"
|
|
|
|
#define BATCH_LINE_LEN_MAX 65536
|
|
#define BATCH_ARG_NB_MAX 4096
|
|
|
|
const char *bin_name;
|
|
static int last_argc;
|
|
static char **last_argv;
|
|
static int (*last_do_help)(int argc, char **argv);
|
|
json_writer_t *json_wtr;
|
|
bool pretty_output;
|
|
bool json_output;
|
|
bool show_pinned;
|
|
bool block_mount;
|
|
int bpf_flags;
|
|
struct pinned_obj_table prog_table;
|
|
struct pinned_obj_table map_table;
|
|
|
|
static void __noreturn clean_and_exit(int i)
|
|
{
|
|
if (json_output)
|
|
jsonw_destroy(&json_wtr);
|
|
|
|
exit(i);
|
|
}
|
|
|
|
void usage(void)
|
|
{
|
|
last_do_help(last_argc - 1, last_argv + 1);
|
|
|
|
clean_and_exit(-1);
|
|
}
|
|
|
|
static int do_help(int argc, char **argv)
|
|
{
|
|
if (json_output) {
|
|
jsonw_null(json_wtr);
|
|
return 0;
|
|
}
|
|
|
|
fprintf(stderr,
|
|
"Usage: %s [OPTIONS] OBJECT { COMMAND | help }\n"
|
|
" %s batch file FILE\n"
|
|
" %s version\n"
|
|
"\n"
|
|
" OBJECT := { prog | map | cgroup | perf | net | feature }\n"
|
|
" " HELP_SPEC_OPTIONS "\n"
|
|
"",
|
|
bin_name, bin_name, bin_name);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int do_version(int argc, char **argv)
|
|
{
|
|
if (json_output) {
|
|
jsonw_start_object(json_wtr);
|
|
jsonw_name(json_wtr, "version");
|
|
jsonw_printf(json_wtr, "\"%s\"", BPFTOOL_VERSION);
|
|
jsonw_end_object(json_wtr);
|
|
} else {
|
|
printf("%s v%s\n", bin_name, BPFTOOL_VERSION);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int cmd_select(const struct cmd *cmds, int argc, char **argv,
|
|
int (*help)(int argc, char **argv))
|
|
{
|
|
unsigned int i;
|
|
|
|
last_argc = argc;
|
|
last_argv = argv;
|
|
last_do_help = help;
|
|
|
|
if (argc < 1 && cmds[0].func)
|
|
return cmds[0].func(argc, argv);
|
|
|
|
for (i = 0; cmds[i].func; i++)
|
|
if (is_prefix(*argv, cmds[i].cmd))
|
|
return cmds[i].func(argc - 1, argv + 1);
|
|
|
|
help(argc - 1, argv + 1);
|
|
|
|
return -1;
|
|
}
|
|
|
|
bool is_prefix(const char *pfx, const char *str)
|
|
{
|
|
if (!pfx)
|
|
return false;
|
|
if (strlen(str) < strlen(pfx))
|
|
return false;
|
|
|
|
return !memcmp(str, pfx, strlen(pfx));
|
|
}
|
|
|
|
void fprint_hex(FILE *f, void *arg, unsigned int n, const char *sep)
|
|
{
|
|
unsigned char *data = arg;
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < n; i++) {
|
|
const char *pfx = "";
|
|
|
|
if (!i)
|
|
/* nothing */;
|
|
else if (!(i % 16))
|
|
fprintf(f, "\n");
|
|
else if (!(i % 8))
|
|
fprintf(f, " ");
|
|
else
|
|
pfx = sep;
|
|
|
|
fprintf(f, "%s%02hhx", i ? pfx : "", data[i]);
|
|
}
|
|
}
|
|
|
|
/* Split command line into argument vector. */
|
|
static int make_args(char *line, char *n_argv[], int maxargs, int cmd_nb)
|
|
{
|
|
static const char ws[] = " \t\r\n";
|
|
char *cp = line;
|
|
int n_argc = 0;
|
|
|
|
while (*cp) {
|
|
/* Skip leading whitespace. */
|
|
cp += strspn(cp, ws);
|
|
|
|
if (*cp == '\0')
|
|
break;
|
|
|
|
if (n_argc >= (maxargs - 1)) {
|
|
p_err("too many arguments to command %d", cmd_nb);
|
|
return -1;
|
|
}
|
|
|
|
/* Word begins with quote. */
|
|
if (*cp == '\'' || *cp == '"') {
|
|
char quote = *cp++;
|
|
|
|
n_argv[n_argc++] = cp;
|
|
/* Find ending quote. */
|
|
cp = strchr(cp, quote);
|
|
if (!cp) {
|
|
p_err("unterminated quoted string in command %d",
|
|
cmd_nb);
|
|
return -1;
|
|
}
|
|
} else {
|
|
n_argv[n_argc++] = cp;
|
|
|
|
/* Find end of word. */
|
|
cp += strcspn(cp, ws);
|
|
if (*cp == '\0')
|
|
break;
|
|
}
|
|
|
|
/* Separate words. */
|
|
*cp++ = 0;
|
|
}
|
|
n_argv[n_argc] = NULL;
|
|
|
|
return n_argc;
|
|
}
|
|
|
|
static int do_batch(int argc, char **argv);
|
|
|
|
static const struct cmd cmds[] = {
|
|
{ "help", do_help },
|
|
{ "batch", do_batch },
|
|
{ "prog", do_prog },
|
|
{ "map", do_map },
|
|
{ "cgroup", do_cgroup },
|
|
{ "perf", do_perf },
|
|
{ "net", do_net },
|
|
{ "feature", do_feature },
|
|
{ "version", do_version },
|
|
{ 0 }
|
|
};
|
|
|
|
static int do_batch(int argc, char **argv)
|
|
{
|
|
char buf[BATCH_LINE_LEN_MAX], contline[BATCH_LINE_LEN_MAX];
|
|
char *n_argv[BATCH_ARG_NB_MAX];
|
|
unsigned int lines = 0;
|
|
int n_argc;
|
|
FILE *fp;
|
|
char *cp;
|
|
int err;
|
|
int i;
|
|
|
|
if (argc < 2) {
|
|
p_err("too few parameters for batch");
|
|
return -1;
|
|
} else if (!is_prefix(*argv, "file")) {
|
|
p_err("expected 'file', got: %s", *argv);
|
|
return -1;
|
|
} else if (argc > 2) {
|
|
p_err("too many parameters for batch");
|
|
return -1;
|
|
}
|
|
NEXT_ARG();
|
|
|
|
if (!strcmp(*argv, "-"))
|
|
fp = stdin;
|
|
else
|
|
fp = fopen(*argv, "r");
|
|
if (!fp) {
|
|
p_err("Can't open file (%s): %s", *argv, strerror(errno));
|
|
return -1;
|
|
}
|
|
|
|
if (json_output)
|
|
jsonw_start_array(json_wtr);
|
|
while (fgets(buf, sizeof(buf), fp)) {
|
|
cp = strchr(buf, '#');
|
|
if (cp)
|
|
*cp = '\0';
|
|
|
|
if (strlen(buf) == sizeof(buf) - 1) {
|
|
errno = E2BIG;
|
|
break;
|
|
}
|
|
|
|
/* Append continuation lines if any (coming after a line ending
|
|
* with '\' in the batch file).
|
|
*/
|
|
while ((cp = strstr(buf, "\\\n")) != NULL) {
|
|
if (!fgets(contline, sizeof(contline), fp) ||
|
|
strlen(contline) == 0) {
|
|
p_err("missing continuation line on command %d",
|
|
lines);
|
|
err = -1;
|
|
goto err_close;
|
|
}
|
|
|
|
cp = strchr(contline, '#');
|
|
if (cp)
|
|
*cp = '\0';
|
|
|
|
if (strlen(buf) + strlen(contline) + 1 > sizeof(buf)) {
|
|
p_err("command %d is too long", lines);
|
|
err = -1;
|
|
goto err_close;
|
|
}
|
|
buf[strlen(buf) - 2] = '\0';
|
|
strcat(buf, contline);
|
|
}
|
|
|
|
n_argc = make_args(buf, n_argv, BATCH_ARG_NB_MAX, lines);
|
|
if (!n_argc)
|
|
continue;
|
|
if (n_argc < 0)
|
|
goto err_close;
|
|
|
|
if (json_output) {
|
|
jsonw_start_object(json_wtr);
|
|
jsonw_name(json_wtr, "command");
|
|
jsonw_start_array(json_wtr);
|
|
for (i = 0; i < n_argc; i++)
|
|
jsonw_string(json_wtr, n_argv[i]);
|
|
jsonw_end_array(json_wtr);
|
|
jsonw_name(json_wtr, "output");
|
|
}
|
|
|
|
err = cmd_select(cmds, n_argc, n_argv, do_help);
|
|
|
|
if (json_output)
|
|
jsonw_end_object(json_wtr);
|
|
|
|
if (err)
|
|
goto err_close;
|
|
|
|
lines++;
|
|
}
|
|
|
|
if (errno && errno != ENOENT) {
|
|
p_err("reading batch file failed: %s", strerror(errno));
|
|
err = -1;
|
|
} else {
|
|
if (!json_output)
|
|
printf("processed %d commands\n", lines);
|
|
err = 0;
|
|
}
|
|
err_close:
|
|
if (fp != stdin)
|
|
fclose(fp);
|
|
|
|
if (json_output)
|
|
jsonw_end_array(json_wtr);
|
|
|
|
return err;
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
static const struct option options[] = {
|
|
{ "json", no_argument, NULL, 'j' },
|
|
{ "help", no_argument, NULL, 'h' },
|
|
{ "pretty", no_argument, NULL, 'p' },
|
|
{ "version", no_argument, NULL, 'V' },
|
|
{ "bpffs", no_argument, NULL, 'f' },
|
|
{ "mapcompat", no_argument, NULL, 'm' },
|
|
{ "nomount", no_argument, NULL, 'n' },
|
|
{ 0 }
|
|
};
|
|
int opt, ret;
|
|
|
|
last_do_help = do_help;
|
|
pretty_output = false;
|
|
json_output = false;
|
|
show_pinned = false;
|
|
block_mount = false;
|
|
bin_name = argv[0];
|
|
|
|
hash_init(prog_table.table);
|
|
hash_init(map_table.table);
|
|
|
|
opterr = 0;
|
|
while ((opt = getopt_long(argc, argv, "Vhpjfmn",
|
|
options, NULL)) >= 0) {
|
|
switch (opt) {
|
|
case 'V':
|
|
return do_version(argc, argv);
|
|
case 'h':
|
|
return do_help(argc, argv);
|
|
case 'p':
|
|
pretty_output = true;
|
|
/* fall through */
|
|
case 'j':
|
|
if (!json_output) {
|
|
json_wtr = jsonw_new(stdout);
|
|
if (!json_wtr) {
|
|
p_err("failed to create JSON writer");
|
|
return -1;
|
|
}
|
|
json_output = true;
|
|
}
|
|
jsonw_pretty(json_wtr, pretty_output);
|
|
break;
|
|
case 'f':
|
|
show_pinned = true;
|
|
break;
|
|
case 'm':
|
|
bpf_flags = MAPS_RELAX_COMPAT;
|
|
break;
|
|
case 'n':
|
|
block_mount = true;
|
|
break;
|
|
default:
|
|
p_err("unrecognized option '%s'", argv[optind - 1]);
|
|
if (json_output)
|
|
clean_and_exit(-1);
|
|
else
|
|
usage();
|
|
}
|
|
}
|
|
|
|
argc -= optind;
|
|
argv += optind;
|
|
if (argc < 0)
|
|
usage();
|
|
|
|
ret = cmd_select(cmds, argc, argv, do_help);
|
|
|
|
if (json_output)
|
|
jsonw_destroy(&json_wtr);
|
|
|
|
if (show_pinned) {
|
|
delete_pinned_obj_table(&prog_table);
|
|
delete_pinned_obj_table(&map_table);
|
|
}
|
|
|
|
return ret;
|
|
}
|