xref: /linux/tools/bpf/bpftool/main.c (revision 7ff836f064e2c814a7504c91a4464eea45d475bd)
1 // SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
2 /* Copyright (C) 2017-2018 Netronome Systems, Inc. */
3 
4 #include <ctype.h>
5 #include <errno.h>
6 #include <getopt.h>
7 #include <linux/bpf.h>
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <string.h>
11 
12 #include <bpf.h>
13 
14 #include "main.h"
15 
16 #define BATCH_LINE_LEN_MAX 65536
17 #define BATCH_ARG_NB_MAX 4096
18 
19 const char *bin_name;
20 static int last_argc;
21 static char **last_argv;
22 static int (*last_do_help)(int argc, char **argv);
23 json_writer_t *json_wtr;
24 bool pretty_output;
25 bool json_output;
26 bool show_pinned;
27 bool block_mount;
28 int bpf_flags;
29 struct pinned_obj_table prog_table;
30 struct pinned_obj_table map_table;
31 
32 static void __noreturn clean_and_exit(int i)
33 {
34 	if (json_output)
35 		jsonw_destroy(&json_wtr);
36 
37 	exit(i);
38 }
39 
40 void usage(void)
41 {
42 	last_do_help(last_argc - 1, last_argv + 1);
43 
44 	clean_and_exit(-1);
45 }
46 
47 static int do_help(int argc, char **argv)
48 {
49 	if (json_output) {
50 		jsonw_null(json_wtr);
51 		return 0;
52 	}
53 
54 	fprintf(stderr,
55 		"Usage: %s [OPTIONS] OBJECT { COMMAND | help }\n"
56 		"       %s batch file FILE\n"
57 		"       %s version\n"
58 		"\n"
59 		"       OBJECT := { prog | map | cgroup | perf | net | feature | btf }\n"
60 		"       " HELP_SPEC_OPTIONS "\n"
61 		"",
62 		bin_name, bin_name, bin_name);
63 
64 	return 0;
65 }
66 
67 static int do_version(int argc, char **argv)
68 {
69 	if (json_output) {
70 		jsonw_start_object(json_wtr);
71 		jsonw_name(json_wtr, "version");
72 		jsonw_printf(json_wtr, "\"%s\"", BPFTOOL_VERSION);
73 		jsonw_end_object(json_wtr);
74 	} else {
75 		printf("%s v%s\n", bin_name, BPFTOOL_VERSION);
76 	}
77 	return 0;
78 }
79 
80 int cmd_select(const struct cmd *cmds, int argc, char **argv,
81 	       int (*help)(int argc, char **argv))
82 {
83 	unsigned int i;
84 
85 	last_argc = argc;
86 	last_argv = argv;
87 	last_do_help = help;
88 
89 	if (argc < 1 && cmds[0].func)
90 		return cmds[0].func(argc, argv);
91 
92 	for (i = 0; cmds[i].func; i++)
93 		if (is_prefix(*argv, cmds[i].cmd))
94 			return cmds[i].func(argc - 1, argv + 1);
95 
96 	help(argc - 1, argv + 1);
97 
98 	return -1;
99 }
100 
101 bool is_prefix(const char *pfx, const char *str)
102 {
103 	if (!pfx)
104 		return false;
105 	if (strlen(str) < strlen(pfx))
106 		return false;
107 
108 	return !memcmp(str, pfx, strlen(pfx));
109 }
110 
111 void fprint_hex(FILE *f, void *arg, unsigned int n, const char *sep)
112 {
113 	unsigned char *data = arg;
114 	unsigned int i;
115 
116 	for (i = 0; i < n; i++) {
117 		const char *pfx = "";
118 
119 		if (!i)
120 			/* nothing */;
121 		else if (!(i % 16))
122 			fprintf(f, "\n");
123 		else if (!(i % 8))
124 			fprintf(f, "  ");
125 		else
126 			pfx = sep;
127 
128 		fprintf(f, "%s%02hhx", i ? pfx : "", data[i]);
129 	}
130 }
131 
132 /* Split command line into argument vector. */
133 static int make_args(char *line, char *n_argv[], int maxargs, int cmd_nb)
134 {
135 	static const char ws[] = " \t\r\n";
136 	char *cp = line;
137 	int n_argc = 0;
138 
139 	while (*cp) {
140 		/* Skip leading whitespace. */
141 		cp += strspn(cp, ws);
142 
143 		if (*cp == '\0')
144 			break;
145 
146 		if (n_argc >= (maxargs - 1)) {
147 			p_err("too many arguments to command %d", cmd_nb);
148 			return -1;
149 		}
150 
151 		/* Word begins with quote. */
152 		if (*cp == '\'' || *cp == '"') {
153 			char quote = *cp++;
154 
155 			n_argv[n_argc++] = cp;
156 			/* Find ending quote. */
157 			cp = strchr(cp, quote);
158 			if (!cp) {
159 				p_err("unterminated quoted string in command %d",
160 				      cmd_nb);
161 				return -1;
162 			}
163 		} else {
164 			n_argv[n_argc++] = cp;
165 
166 			/* Find end of word. */
167 			cp += strcspn(cp, ws);
168 			if (*cp == '\0')
169 				break;
170 		}
171 
172 		/* Separate words. */
173 		*cp++ = 0;
174 	}
175 	n_argv[n_argc] = NULL;
176 
177 	return n_argc;
178 }
179 
180 static int do_batch(int argc, char **argv);
181 
182 static const struct cmd cmds[] = {
183 	{ "help",	do_help },
184 	{ "batch",	do_batch },
185 	{ "prog",	do_prog },
186 	{ "map",	do_map },
187 	{ "cgroup",	do_cgroup },
188 	{ "perf",	do_perf },
189 	{ "net",	do_net },
190 	{ "feature",	do_feature },
191 	{ "btf",	do_btf },
192 	{ "version",	do_version },
193 	{ 0 }
194 };
195 
196 static int do_batch(int argc, char **argv)
197 {
198 	char buf[BATCH_LINE_LEN_MAX], contline[BATCH_LINE_LEN_MAX];
199 	char *n_argv[BATCH_ARG_NB_MAX];
200 	unsigned int lines = 0;
201 	int n_argc;
202 	FILE *fp;
203 	char *cp;
204 	int err;
205 	int i;
206 
207 	if (argc < 2) {
208 		p_err("too few parameters for batch");
209 		return -1;
210 	} else if (!is_prefix(*argv, "file")) {
211 		p_err("expected 'file', got: %s", *argv);
212 		return -1;
213 	} else if (argc > 2) {
214 		p_err("too many parameters for batch");
215 		return -1;
216 	}
217 	NEXT_ARG();
218 
219 	if (!strcmp(*argv, "-"))
220 		fp = stdin;
221 	else
222 		fp = fopen(*argv, "r");
223 	if (!fp) {
224 		p_err("Can't open file (%s): %s", *argv, strerror(errno));
225 		return -1;
226 	}
227 
228 	if (json_output)
229 		jsonw_start_array(json_wtr);
230 	while (fgets(buf, sizeof(buf), fp)) {
231 		cp = strchr(buf, '#');
232 		if (cp)
233 			*cp = '\0';
234 
235 		if (strlen(buf) == sizeof(buf) - 1) {
236 			errno = E2BIG;
237 			break;
238 		}
239 
240 		/* Append continuation lines if any (coming after a line ending
241 		 * with '\' in the batch file).
242 		 */
243 		while ((cp = strstr(buf, "\\\n")) != NULL) {
244 			if (!fgets(contline, sizeof(contline), fp) ||
245 			    strlen(contline) == 0) {
246 				p_err("missing continuation line on command %d",
247 				      lines);
248 				err = -1;
249 				goto err_close;
250 			}
251 
252 			cp = strchr(contline, '#');
253 			if (cp)
254 				*cp = '\0';
255 
256 			if (strlen(buf) + strlen(contline) + 1 > sizeof(buf)) {
257 				p_err("command %d is too long", lines);
258 				err = -1;
259 				goto err_close;
260 			}
261 			buf[strlen(buf) - 2] = '\0';
262 			strcat(buf, contline);
263 		}
264 
265 		n_argc = make_args(buf, n_argv, BATCH_ARG_NB_MAX, lines);
266 		if (!n_argc)
267 			continue;
268 		if (n_argc < 0)
269 			goto err_close;
270 
271 		if (json_output) {
272 			jsonw_start_object(json_wtr);
273 			jsonw_name(json_wtr, "command");
274 			jsonw_start_array(json_wtr);
275 			for (i = 0; i < n_argc; i++)
276 				jsonw_string(json_wtr, n_argv[i]);
277 			jsonw_end_array(json_wtr);
278 			jsonw_name(json_wtr, "output");
279 		}
280 
281 		err = cmd_select(cmds, n_argc, n_argv, do_help);
282 
283 		if (json_output)
284 			jsonw_end_object(json_wtr);
285 
286 		if (err)
287 			goto err_close;
288 
289 		lines++;
290 	}
291 
292 	if (errno && errno != ENOENT) {
293 		p_err("reading batch file failed: %s", strerror(errno));
294 		err = -1;
295 	} else {
296 		if (!json_output)
297 			printf("processed %d commands\n", lines);
298 		err = 0;
299 	}
300 err_close:
301 	if (fp != stdin)
302 		fclose(fp);
303 
304 	if (json_output)
305 		jsonw_end_array(json_wtr);
306 
307 	return err;
308 }
309 
310 int main(int argc, char **argv)
311 {
312 	static const struct option options[] = {
313 		{ "json",	no_argument,	NULL,	'j' },
314 		{ "help",	no_argument,	NULL,	'h' },
315 		{ "pretty",	no_argument,	NULL,	'p' },
316 		{ "version",	no_argument,	NULL,	'V' },
317 		{ "bpffs",	no_argument,	NULL,	'f' },
318 		{ "mapcompat",	no_argument,	NULL,	'm' },
319 		{ "nomount",	no_argument,	NULL,	'n' },
320 		{ 0 }
321 	};
322 	int opt, ret;
323 
324 	last_do_help = do_help;
325 	pretty_output = false;
326 	json_output = false;
327 	show_pinned = false;
328 	block_mount = false;
329 	bin_name = argv[0];
330 
331 	hash_init(prog_table.table);
332 	hash_init(map_table.table);
333 
334 	opterr = 0;
335 	while ((opt = getopt_long(argc, argv, "Vhpjfmn",
336 				  options, NULL)) >= 0) {
337 		switch (opt) {
338 		case 'V':
339 			return do_version(argc, argv);
340 		case 'h':
341 			return do_help(argc, argv);
342 		case 'p':
343 			pretty_output = true;
344 			/* fall through */
345 		case 'j':
346 			if (!json_output) {
347 				json_wtr = jsonw_new(stdout);
348 				if (!json_wtr) {
349 					p_err("failed to create JSON writer");
350 					return -1;
351 				}
352 				json_output = true;
353 			}
354 			jsonw_pretty(json_wtr, pretty_output);
355 			break;
356 		case 'f':
357 			show_pinned = true;
358 			break;
359 		case 'm':
360 			bpf_flags = MAPS_RELAX_COMPAT;
361 			break;
362 		case 'n':
363 			block_mount = true;
364 			break;
365 		default:
366 			p_err("unrecognized option '%s'", argv[optind - 1]);
367 			if (json_output)
368 				clean_and_exit(-1);
369 			else
370 				usage();
371 		}
372 	}
373 
374 	argc -= optind;
375 	argv += optind;
376 	if (argc < 0)
377 		usage();
378 
379 	ret = cmd_select(cmds, argc, argv, do_help);
380 
381 	if (json_output)
382 		jsonw_destroy(&json_wtr);
383 
384 	if (show_pinned) {
385 		delete_pinned_obj_table(&prog_table);
386 		delete_pinned_obj_table(&map_table);
387 	}
388 
389 	return ret;
390 }
391