xref: /linux/tools/bpf/bpftool/main.c (revision b60a5b8dcf49af9f2c60ae82e0383ee8e62a9a52)
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 }\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 	{ "version",	do_version },
192 	{ 0 }
193 };
194 
195 static int do_batch(int argc, char **argv)
196 {
197 	char buf[BATCH_LINE_LEN_MAX], contline[BATCH_LINE_LEN_MAX];
198 	char *n_argv[BATCH_ARG_NB_MAX];
199 	unsigned int lines = 0;
200 	int n_argc;
201 	FILE *fp;
202 	char *cp;
203 	int err;
204 	int i;
205 
206 	if (argc < 2) {
207 		p_err("too few parameters for batch");
208 		return -1;
209 	} else if (!is_prefix(*argv, "file")) {
210 		p_err("expected 'file', got: %s", *argv);
211 		return -1;
212 	} else if (argc > 2) {
213 		p_err("too many parameters for batch");
214 		return -1;
215 	}
216 	NEXT_ARG();
217 
218 	if (!strcmp(*argv, "-"))
219 		fp = stdin;
220 	else
221 		fp = fopen(*argv, "r");
222 	if (!fp) {
223 		p_err("Can't open file (%s): %s", *argv, strerror(errno));
224 		return -1;
225 	}
226 
227 	if (json_output)
228 		jsonw_start_array(json_wtr);
229 	while (fgets(buf, sizeof(buf), fp)) {
230 		cp = strchr(buf, '#');
231 		if (cp)
232 			*cp = '\0';
233 
234 		if (strlen(buf) == sizeof(buf) - 1) {
235 			errno = E2BIG;
236 			break;
237 		}
238 
239 		/* Append continuation lines if any (coming after a line ending
240 		 * with '\' in the batch file).
241 		 */
242 		while ((cp = strstr(buf, "\\\n")) != NULL) {
243 			if (!fgets(contline, sizeof(contline), fp) ||
244 			    strlen(contline) == 0) {
245 				p_err("missing continuation line on command %d",
246 				      lines);
247 				err = -1;
248 				goto err_close;
249 			}
250 
251 			cp = strchr(contline, '#');
252 			if (cp)
253 				*cp = '\0';
254 
255 			if (strlen(buf) + strlen(contline) + 1 > sizeof(buf)) {
256 				p_err("command %d is too long", lines);
257 				err = -1;
258 				goto err_close;
259 			}
260 			buf[strlen(buf) - 2] = '\0';
261 			strcat(buf, contline);
262 		}
263 
264 		n_argc = make_args(buf, n_argv, BATCH_ARG_NB_MAX, lines);
265 		if (!n_argc)
266 			continue;
267 		if (n_argc < 0)
268 			goto err_close;
269 
270 		if (json_output) {
271 			jsonw_start_object(json_wtr);
272 			jsonw_name(json_wtr, "command");
273 			jsonw_start_array(json_wtr);
274 			for (i = 0; i < n_argc; i++)
275 				jsonw_string(json_wtr, n_argv[i]);
276 			jsonw_end_array(json_wtr);
277 			jsonw_name(json_wtr, "output");
278 		}
279 
280 		err = cmd_select(cmds, n_argc, n_argv, do_help);
281 
282 		if (json_output)
283 			jsonw_end_object(json_wtr);
284 
285 		if (err)
286 			goto err_close;
287 
288 		lines++;
289 	}
290 
291 	if (errno && errno != ENOENT) {
292 		p_err("reading batch file failed: %s", strerror(errno));
293 		err = -1;
294 	} else {
295 		if (!json_output)
296 			printf("processed %d commands\n", lines);
297 		err = 0;
298 	}
299 err_close:
300 	if (fp != stdin)
301 		fclose(fp);
302 
303 	if (json_output)
304 		jsonw_end_array(json_wtr);
305 
306 	return err;
307 }
308 
309 int main(int argc, char **argv)
310 {
311 	static const struct option options[] = {
312 		{ "json",	no_argument,	NULL,	'j' },
313 		{ "help",	no_argument,	NULL,	'h' },
314 		{ "pretty",	no_argument,	NULL,	'p' },
315 		{ "version",	no_argument,	NULL,	'V' },
316 		{ "bpffs",	no_argument,	NULL,	'f' },
317 		{ "mapcompat",	no_argument,	NULL,	'm' },
318 		{ "nomount",	no_argument,	NULL,	'n' },
319 		{ 0 }
320 	};
321 	int opt, ret;
322 
323 	last_do_help = do_help;
324 	pretty_output = false;
325 	json_output = false;
326 	show_pinned = false;
327 	block_mount = false;
328 	bin_name = argv[0];
329 
330 	hash_init(prog_table.table);
331 	hash_init(map_table.table);
332 
333 	opterr = 0;
334 	while ((opt = getopt_long(argc, argv, "Vhpjfmn",
335 				  options, NULL)) >= 0) {
336 		switch (opt) {
337 		case 'V':
338 			return do_version(argc, argv);
339 		case 'h':
340 			return do_help(argc, argv);
341 		case 'p':
342 			pretty_output = true;
343 			/* fall through */
344 		case 'j':
345 			if (!json_output) {
346 				json_wtr = jsonw_new(stdout);
347 				if (!json_wtr) {
348 					p_err("failed to create JSON writer");
349 					return -1;
350 				}
351 				json_output = true;
352 			}
353 			jsonw_pretty(json_wtr, pretty_output);
354 			break;
355 		case 'f':
356 			show_pinned = true;
357 			break;
358 		case 'm':
359 			bpf_flags = MAPS_RELAX_COMPAT;
360 			break;
361 		case 'n':
362 			block_mount = true;
363 			break;
364 		default:
365 			p_err("unrecognized option '%s'", argv[optind - 1]);
366 			if (json_output)
367 				clean_and_exit(-1);
368 			else
369 				usage();
370 		}
371 	}
372 
373 	argc -= optind;
374 	argv += optind;
375 	if (argc < 0)
376 		usage();
377 
378 	ret = cmd_select(cmds, argc, argv, do_help);
379 
380 	if (json_output)
381 		jsonw_destroy(&json_wtr);
382 
383 	if (show_pinned) {
384 		delete_pinned_obj_table(&prog_table);
385 		delete_pinned_obj_table(&map_table);
386 	}
387 
388 	return ret;
389 }
390