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