veristat.c 32 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322
  1. // SPDX-License-Identifier: GPL-2.0
  2. /* Copyright (c) 2022 Meta Platforms, Inc. and affiliates. */
  3. #define _GNU_SOURCE
  4. #include <argp.h>
  5. #include <string.h>
  6. #include <stdlib.h>
  7. #include <linux/compiler.h>
  8. #include <sched.h>
  9. #include <pthread.h>
  10. #include <dirent.h>
  11. #include <signal.h>
  12. #include <fcntl.h>
  13. #include <unistd.h>
  14. #include <sys/time.h>
  15. #include <sys/sysinfo.h>
  16. #include <sys/stat.h>
  17. #include <bpf/libbpf.h>
  18. #include <libelf.h>
  19. #include <gelf.h>
  20. enum stat_id {
  21. VERDICT,
  22. DURATION,
  23. TOTAL_INSNS,
  24. TOTAL_STATES,
  25. PEAK_STATES,
  26. MAX_STATES_PER_INSN,
  27. MARK_READ_MAX_LEN,
  28. FILE_NAME,
  29. PROG_NAME,
  30. ALL_STATS_CNT,
  31. NUM_STATS_CNT = FILE_NAME - VERDICT,
  32. };
  33. struct verif_stats {
  34. char *file_name;
  35. char *prog_name;
  36. long stats[NUM_STATS_CNT];
  37. };
  38. struct stat_specs {
  39. int spec_cnt;
  40. enum stat_id ids[ALL_STATS_CNT];
  41. bool asc[ALL_STATS_CNT];
  42. int lens[ALL_STATS_CNT * 3]; /* 3x for comparison mode */
  43. };
  44. enum resfmt {
  45. RESFMT_TABLE,
  46. RESFMT_TABLE_CALCLEN, /* fake format to pre-calculate table's column widths */
  47. RESFMT_CSV,
  48. };
  49. struct filter {
  50. char *file_glob;
  51. char *prog_glob;
  52. };
  53. static struct env {
  54. char **filenames;
  55. int filename_cnt;
  56. bool verbose;
  57. bool quiet;
  58. int log_level;
  59. enum resfmt out_fmt;
  60. bool comparison_mode;
  61. struct verif_stats *prog_stats;
  62. int prog_stat_cnt;
  63. /* baseline_stats is allocated and used only in comparsion mode */
  64. struct verif_stats *baseline_stats;
  65. int baseline_stat_cnt;
  66. struct stat_specs output_spec;
  67. struct stat_specs sort_spec;
  68. struct filter *allow_filters;
  69. struct filter *deny_filters;
  70. int allow_filter_cnt;
  71. int deny_filter_cnt;
  72. int files_processed;
  73. int files_skipped;
  74. int progs_processed;
  75. int progs_skipped;
  76. } env;
  77. static int libbpf_print_fn(enum libbpf_print_level level, const char *format, va_list args)
  78. {
  79. if (!env.verbose)
  80. return 0;
  81. if (level == LIBBPF_DEBUG /* && !env.verbose */)
  82. return 0;
  83. return vfprintf(stderr, format, args);
  84. }
  85. const char *argp_program_version = "veristat";
  86. const char *argp_program_bug_address = "<[email protected]>";
  87. const char argp_program_doc[] =
  88. "veristat BPF verifier stats collection and comparison tool.\n"
  89. "\n"
  90. "USAGE: veristat <obj-file> [<obj-file>...]\n"
  91. " OR: veristat -C <baseline.csv> <comparison.csv>\n";
  92. static const struct argp_option opts[] = {
  93. { NULL, 'h', NULL, OPTION_HIDDEN, "Show the full help" },
  94. { "verbose", 'v', NULL, 0, "Verbose mode" },
  95. { "log-level", 'l', "LEVEL", 0, "Verifier log level (default 0 for normal mode, 1 for verbose mode)" },
  96. { "quiet", 'q', NULL, 0, "Quiet mode" },
  97. { "emit", 'e', "SPEC", 0, "Specify stats to be emitted" },
  98. { "sort", 's', "SPEC", 0, "Specify sort order" },
  99. { "output-format", 'o', "FMT", 0, "Result output format (table, csv), default is table." },
  100. { "compare", 'C', NULL, 0, "Comparison mode" },
  101. { "filter", 'f', "FILTER", 0, "Filter expressions (or @filename for file with expressions)." },
  102. {},
  103. };
  104. static int parse_stats(const char *stats_str, struct stat_specs *specs);
  105. static int append_filter(struct filter **filters, int *cnt, const char *str);
  106. static int append_filter_file(const char *path);
  107. static error_t parse_arg(int key, char *arg, struct argp_state *state)
  108. {
  109. void *tmp;
  110. int err;
  111. switch (key) {
  112. case 'h':
  113. argp_state_help(state, stderr, ARGP_HELP_STD_HELP);
  114. break;
  115. case 'v':
  116. env.verbose = true;
  117. break;
  118. case 'q':
  119. env.quiet = true;
  120. break;
  121. case 'e':
  122. err = parse_stats(arg, &env.output_spec);
  123. if (err)
  124. return err;
  125. break;
  126. case 's':
  127. err = parse_stats(arg, &env.sort_spec);
  128. if (err)
  129. return err;
  130. break;
  131. case 'o':
  132. if (strcmp(arg, "table") == 0) {
  133. env.out_fmt = RESFMT_TABLE;
  134. } else if (strcmp(arg, "csv") == 0) {
  135. env.out_fmt = RESFMT_CSV;
  136. } else {
  137. fprintf(stderr, "Unrecognized output format '%s'\n", arg);
  138. return -EINVAL;
  139. }
  140. break;
  141. case 'l':
  142. errno = 0;
  143. env.log_level = strtol(arg, NULL, 10);
  144. if (errno) {
  145. fprintf(stderr, "invalid log level: %s\n", arg);
  146. argp_usage(state);
  147. }
  148. break;
  149. case 'C':
  150. env.comparison_mode = true;
  151. break;
  152. case 'f':
  153. if (arg[0] == '@')
  154. err = append_filter_file(arg + 1);
  155. else if (arg[0] == '!')
  156. err = append_filter(&env.deny_filters, &env.deny_filter_cnt, arg + 1);
  157. else
  158. err = append_filter(&env.allow_filters, &env.allow_filter_cnt, arg);
  159. if (err) {
  160. fprintf(stderr, "Failed to collect program filter expressions: %d\n", err);
  161. return err;
  162. }
  163. break;
  164. case ARGP_KEY_ARG:
  165. tmp = realloc(env.filenames, (env.filename_cnt + 1) * sizeof(*env.filenames));
  166. if (!tmp)
  167. return -ENOMEM;
  168. env.filenames = tmp;
  169. env.filenames[env.filename_cnt] = strdup(arg);
  170. if (!env.filenames[env.filename_cnt])
  171. return -ENOMEM;
  172. env.filename_cnt++;
  173. break;
  174. default:
  175. return ARGP_ERR_UNKNOWN;
  176. }
  177. return 0;
  178. }
  179. static const struct argp argp = {
  180. .options = opts,
  181. .parser = parse_arg,
  182. .doc = argp_program_doc,
  183. };
  184. /* Adapted from perf/util/string.c */
  185. static bool glob_matches(const char *str, const char *pat)
  186. {
  187. while (*str && *pat && *pat != '*') {
  188. if (*str != *pat)
  189. return false;
  190. str++;
  191. pat++;
  192. }
  193. /* Check wild card */
  194. if (*pat == '*') {
  195. while (*pat == '*')
  196. pat++;
  197. if (!*pat) /* Tail wild card matches all */
  198. return true;
  199. while (*str)
  200. if (glob_matches(str++, pat))
  201. return true;
  202. }
  203. return !*str && !*pat;
  204. }
  205. static bool should_process_file(const char *filename)
  206. {
  207. int i;
  208. if (env.deny_filter_cnt > 0) {
  209. for (i = 0; i < env.deny_filter_cnt; i++) {
  210. if (glob_matches(filename, env.deny_filters[i].file_glob))
  211. return false;
  212. }
  213. }
  214. if (env.allow_filter_cnt == 0)
  215. return true;
  216. for (i = 0; i < env.allow_filter_cnt; i++) {
  217. if (glob_matches(filename, env.allow_filters[i].file_glob))
  218. return true;
  219. }
  220. return false;
  221. }
  222. static bool is_bpf_obj_file(const char *path) {
  223. Elf64_Ehdr *ehdr;
  224. int fd, err = -EINVAL;
  225. Elf *elf = NULL;
  226. fd = open(path, O_RDONLY | O_CLOEXEC);
  227. if (fd < 0)
  228. return true; /* we'll fail later and propagate error */
  229. /* ensure libelf is initialized */
  230. (void)elf_version(EV_CURRENT);
  231. elf = elf_begin(fd, ELF_C_READ, NULL);
  232. if (!elf)
  233. goto cleanup;
  234. if (elf_kind(elf) != ELF_K_ELF || gelf_getclass(elf) != ELFCLASS64)
  235. goto cleanup;
  236. ehdr = elf64_getehdr(elf);
  237. /* Old LLVM set e_machine to EM_NONE */
  238. if (!ehdr || ehdr->e_type != ET_REL || (ehdr->e_machine && ehdr->e_machine != EM_BPF))
  239. goto cleanup;
  240. err = 0;
  241. cleanup:
  242. if (elf)
  243. elf_end(elf);
  244. close(fd);
  245. return err == 0;
  246. }
  247. static bool should_process_prog(const char *path, const char *prog_name)
  248. {
  249. const char *filename = basename(path);
  250. int i;
  251. if (env.deny_filter_cnt > 0) {
  252. for (i = 0; i < env.deny_filter_cnt; i++) {
  253. if (glob_matches(filename, env.deny_filters[i].file_glob))
  254. return false;
  255. if (!env.deny_filters[i].prog_glob)
  256. continue;
  257. if (glob_matches(prog_name, env.deny_filters[i].prog_glob))
  258. return false;
  259. }
  260. }
  261. if (env.allow_filter_cnt == 0)
  262. return true;
  263. for (i = 0; i < env.allow_filter_cnt; i++) {
  264. if (!glob_matches(filename, env.allow_filters[i].file_glob))
  265. continue;
  266. /* if filter specifies only filename glob part, it implicitly
  267. * allows all progs within that file
  268. */
  269. if (!env.allow_filters[i].prog_glob)
  270. return true;
  271. if (glob_matches(prog_name, env.allow_filters[i].prog_glob))
  272. return true;
  273. }
  274. return false;
  275. }
  276. static int append_filter(struct filter **filters, int *cnt, const char *str)
  277. {
  278. struct filter *f;
  279. void *tmp;
  280. const char *p;
  281. tmp = realloc(*filters, (*cnt + 1) * sizeof(**filters));
  282. if (!tmp)
  283. return -ENOMEM;
  284. *filters = tmp;
  285. f = &(*filters)[*cnt];
  286. f->file_glob = f->prog_glob = NULL;
  287. /* filter can be specified either as "<obj-glob>" or "<obj-glob>/<prog-glob>" */
  288. p = strchr(str, '/');
  289. if (!p) {
  290. f->file_glob = strdup(str);
  291. if (!f->file_glob)
  292. return -ENOMEM;
  293. } else {
  294. f->file_glob = strndup(str, p - str);
  295. f->prog_glob = strdup(p + 1);
  296. if (!f->file_glob || !f->prog_glob) {
  297. free(f->file_glob);
  298. free(f->prog_glob);
  299. f->file_glob = f->prog_glob = NULL;
  300. return -ENOMEM;
  301. }
  302. }
  303. *cnt = *cnt + 1;
  304. return 0;
  305. }
  306. static int append_filter_file(const char *path)
  307. {
  308. char buf[1024];
  309. FILE *f;
  310. int err = 0;
  311. f = fopen(path, "r");
  312. if (!f) {
  313. err = -errno;
  314. fprintf(stderr, "Failed to open filters in '%s': %d\n", path, err);
  315. return err;
  316. }
  317. while (fscanf(f, " %1023[^\n]\n", buf) == 1) {
  318. /* lines starting with # are comments, skip them */
  319. if (buf[0] == '\0' || buf[0] == '#')
  320. continue;
  321. /* lines starting with ! are negative match filters */
  322. if (buf[0] == '!')
  323. err = append_filter(&env.deny_filters, &env.deny_filter_cnt, buf + 1);
  324. else
  325. err = append_filter(&env.allow_filters, &env.allow_filter_cnt, buf);
  326. if (err)
  327. goto cleanup;
  328. }
  329. cleanup:
  330. fclose(f);
  331. return err;
  332. }
  333. static const struct stat_specs default_output_spec = {
  334. .spec_cnt = 7,
  335. .ids = {
  336. FILE_NAME, PROG_NAME, VERDICT, DURATION,
  337. TOTAL_INSNS, TOTAL_STATES, PEAK_STATES,
  338. },
  339. };
  340. static const struct stat_specs default_sort_spec = {
  341. .spec_cnt = 2,
  342. .ids = {
  343. FILE_NAME, PROG_NAME,
  344. },
  345. .asc = { true, true, },
  346. };
  347. static struct stat_def {
  348. const char *header;
  349. const char *names[4];
  350. bool asc_by_default;
  351. } stat_defs[] = {
  352. [FILE_NAME] = { "File", {"file_name", "filename", "file"}, true /* asc */ },
  353. [PROG_NAME] = { "Program", {"prog_name", "progname", "prog"}, true /* asc */ },
  354. [VERDICT] = { "Verdict", {"verdict"}, true /* asc: failure, success */ },
  355. [DURATION] = { "Duration (us)", {"duration", "dur"}, },
  356. [TOTAL_INSNS] = { "Total insns", {"total_insns", "insns"}, },
  357. [TOTAL_STATES] = { "Total states", {"total_states", "states"}, },
  358. [PEAK_STATES] = { "Peak states", {"peak_states"}, },
  359. [MAX_STATES_PER_INSN] = { "Max states per insn", {"max_states_per_insn"}, },
  360. [MARK_READ_MAX_LEN] = { "Max mark read length", {"max_mark_read_len", "mark_read"}, },
  361. };
  362. static int parse_stat(const char *stat_name, struct stat_specs *specs)
  363. {
  364. int id, i;
  365. if (specs->spec_cnt >= ARRAY_SIZE(specs->ids)) {
  366. fprintf(stderr, "Can't specify more than %zd stats\n", ARRAY_SIZE(specs->ids));
  367. return -E2BIG;
  368. }
  369. for (id = 0; id < ARRAY_SIZE(stat_defs); id++) {
  370. struct stat_def *def = &stat_defs[id];
  371. for (i = 0; i < ARRAY_SIZE(stat_defs[id].names); i++) {
  372. if (!def->names[i] || strcmp(def->names[i], stat_name) != 0)
  373. continue;
  374. specs->ids[specs->spec_cnt] = id;
  375. specs->asc[specs->spec_cnt] = def->asc_by_default;
  376. specs->spec_cnt++;
  377. return 0;
  378. }
  379. }
  380. fprintf(stderr, "Unrecognized stat name '%s'\n", stat_name);
  381. return -ESRCH;
  382. }
  383. static int parse_stats(const char *stats_str, struct stat_specs *specs)
  384. {
  385. char *input, *state = NULL, *next;
  386. int err;
  387. input = strdup(stats_str);
  388. if (!input)
  389. return -ENOMEM;
  390. while ((next = strtok_r(state ? NULL : input, ",", &state))) {
  391. err = parse_stat(next, specs);
  392. if (err)
  393. return err;
  394. }
  395. return 0;
  396. }
  397. static void free_verif_stats(struct verif_stats *stats, size_t stat_cnt)
  398. {
  399. int i;
  400. if (!stats)
  401. return;
  402. for (i = 0; i < stat_cnt; i++) {
  403. free(stats[i].file_name);
  404. free(stats[i].prog_name);
  405. }
  406. free(stats);
  407. }
  408. static char verif_log_buf[64 * 1024];
  409. #define MAX_PARSED_LOG_LINES 100
  410. static int parse_verif_log(char * const buf, size_t buf_sz, struct verif_stats *s)
  411. {
  412. const char *cur;
  413. int pos, lines;
  414. buf[buf_sz - 1] = '\0';
  415. for (pos = strlen(buf) - 1, lines = 0; pos >= 0 && lines < MAX_PARSED_LOG_LINES; lines++) {
  416. /* find previous endline or otherwise take the start of log buf */
  417. for (cur = &buf[pos]; cur > buf && cur[0] != '\n'; cur--, pos--) {
  418. }
  419. /* next time start from end of previous line (or pos goes to <0) */
  420. pos--;
  421. /* if we found endline, point right after endline symbol;
  422. * otherwise, stay at the beginning of log buf
  423. */
  424. if (cur[0] == '\n')
  425. cur++;
  426. if (1 == sscanf(cur, "verification time %ld usec\n", &s->stats[DURATION]))
  427. continue;
  428. if (6 == sscanf(cur, "processed %ld insns (limit %*d) max_states_per_insn %ld total_states %ld peak_states %ld mark_read %ld",
  429. &s->stats[TOTAL_INSNS],
  430. &s->stats[MAX_STATES_PER_INSN],
  431. &s->stats[TOTAL_STATES],
  432. &s->stats[PEAK_STATES],
  433. &s->stats[MARK_READ_MAX_LEN]))
  434. continue;
  435. }
  436. return 0;
  437. }
  438. static int process_prog(const char *filename, struct bpf_object *obj, struct bpf_program *prog)
  439. {
  440. const char *prog_name = bpf_program__name(prog);
  441. size_t buf_sz = sizeof(verif_log_buf);
  442. char *buf = verif_log_buf;
  443. struct verif_stats *stats;
  444. int err = 0;
  445. void *tmp;
  446. if (!should_process_prog(filename, bpf_program__name(prog))) {
  447. env.progs_skipped++;
  448. return 0;
  449. }
  450. tmp = realloc(env.prog_stats, (env.prog_stat_cnt + 1) * sizeof(*env.prog_stats));
  451. if (!tmp)
  452. return -ENOMEM;
  453. env.prog_stats = tmp;
  454. stats = &env.prog_stats[env.prog_stat_cnt++];
  455. memset(stats, 0, sizeof(*stats));
  456. if (env.verbose) {
  457. buf_sz = 16 * 1024 * 1024;
  458. buf = malloc(buf_sz);
  459. if (!buf)
  460. return -ENOMEM;
  461. bpf_program__set_log_buf(prog, buf, buf_sz);
  462. bpf_program__set_log_level(prog, env.log_level | 4); /* stats + log */
  463. } else {
  464. bpf_program__set_log_buf(prog, buf, buf_sz);
  465. bpf_program__set_log_level(prog, 4); /* only verifier stats */
  466. }
  467. verif_log_buf[0] = '\0';
  468. err = bpf_object__load(obj);
  469. env.progs_processed++;
  470. stats->file_name = strdup(basename(filename));
  471. stats->prog_name = strdup(bpf_program__name(prog));
  472. stats->stats[VERDICT] = err == 0; /* 1 - success, 0 - failure */
  473. parse_verif_log(buf, buf_sz, stats);
  474. if (env.verbose) {
  475. printf("PROCESSING %s/%s, DURATION US: %ld, VERDICT: %s, VERIFIER LOG:\n%s\n",
  476. filename, prog_name, stats->stats[DURATION],
  477. err ? "failure" : "success", buf);
  478. }
  479. if (verif_log_buf != buf)
  480. free(buf);
  481. return 0;
  482. };
  483. static int process_obj(const char *filename)
  484. {
  485. struct bpf_object *obj = NULL, *tobj;
  486. struct bpf_program *prog, *tprog, *lprog;
  487. libbpf_print_fn_t old_libbpf_print_fn;
  488. LIBBPF_OPTS(bpf_object_open_opts, opts);
  489. int err = 0, prog_cnt = 0;
  490. if (!should_process_file(basename(filename))) {
  491. if (env.verbose)
  492. printf("Skipping '%s' due to filters...\n", filename);
  493. env.files_skipped++;
  494. return 0;
  495. }
  496. if (!is_bpf_obj_file(filename)) {
  497. if (env.verbose)
  498. printf("Skipping '%s' as it's not a BPF object file...\n", filename);
  499. env.files_skipped++;
  500. return 0;
  501. }
  502. if (!env.quiet && env.out_fmt == RESFMT_TABLE)
  503. printf("Processing '%s'...\n", basename(filename));
  504. old_libbpf_print_fn = libbpf_set_print(libbpf_print_fn);
  505. obj = bpf_object__open_file(filename, &opts);
  506. if (!obj) {
  507. /* if libbpf can't open BPF object file, it could be because
  508. * that BPF object file is incomplete and has to be statically
  509. * linked into a final BPF object file; instead of bailing
  510. * out, report it into stderr, mark it as skipped, and
  511. * proceeed
  512. */
  513. fprintf(stderr, "Failed to open '%s': %d\n", filename, -errno);
  514. env.files_skipped++;
  515. err = 0;
  516. goto cleanup;
  517. }
  518. env.files_processed++;
  519. bpf_object__for_each_program(prog, obj) {
  520. prog_cnt++;
  521. }
  522. if (prog_cnt == 1) {
  523. prog = bpf_object__next_program(obj, NULL);
  524. bpf_program__set_autoload(prog, true);
  525. process_prog(filename, obj, prog);
  526. goto cleanup;
  527. }
  528. bpf_object__for_each_program(prog, obj) {
  529. const char *prog_name = bpf_program__name(prog);
  530. tobj = bpf_object__open_file(filename, &opts);
  531. if (!tobj) {
  532. err = -errno;
  533. fprintf(stderr, "Failed to open '%s': %d\n", filename, err);
  534. goto cleanup;
  535. }
  536. bpf_object__for_each_program(tprog, tobj) {
  537. const char *tprog_name = bpf_program__name(tprog);
  538. if (strcmp(prog_name, tprog_name) == 0) {
  539. bpf_program__set_autoload(tprog, true);
  540. lprog = tprog;
  541. } else {
  542. bpf_program__set_autoload(tprog, false);
  543. }
  544. }
  545. process_prog(filename, tobj, lprog);
  546. bpf_object__close(tobj);
  547. }
  548. cleanup:
  549. bpf_object__close(obj);
  550. libbpf_set_print(old_libbpf_print_fn);
  551. return err;
  552. }
  553. static int cmp_stat(const struct verif_stats *s1, const struct verif_stats *s2,
  554. enum stat_id id, bool asc)
  555. {
  556. int cmp = 0;
  557. switch (id) {
  558. case FILE_NAME:
  559. cmp = strcmp(s1->file_name, s2->file_name);
  560. break;
  561. case PROG_NAME:
  562. cmp = strcmp(s1->prog_name, s2->prog_name);
  563. break;
  564. case VERDICT:
  565. case DURATION:
  566. case TOTAL_INSNS:
  567. case TOTAL_STATES:
  568. case PEAK_STATES:
  569. case MAX_STATES_PER_INSN:
  570. case MARK_READ_MAX_LEN: {
  571. long v1 = s1->stats[id];
  572. long v2 = s2->stats[id];
  573. if (v1 != v2)
  574. cmp = v1 < v2 ? -1 : 1;
  575. break;
  576. }
  577. default:
  578. fprintf(stderr, "Unrecognized stat #%d\n", id);
  579. exit(1);
  580. }
  581. return asc ? cmp : -cmp;
  582. }
  583. static int cmp_prog_stats(const void *v1, const void *v2)
  584. {
  585. const struct verif_stats *s1 = v1, *s2 = v2;
  586. int i, cmp;
  587. for (i = 0; i < env.sort_spec.spec_cnt; i++) {
  588. cmp = cmp_stat(s1, s2, env.sort_spec.ids[i], env.sort_spec.asc[i]);
  589. if (cmp != 0)
  590. return cmp;
  591. }
  592. return 0;
  593. }
  594. #define HEADER_CHAR '-'
  595. #define COLUMN_SEP " "
  596. static void output_header_underlines(void)
  597. {
  598. int i, j, len;
  599. for (i = 0; i < env.output_spec.spec_cnt; i++) {
  600. len = env.output_spec.lens[i];
  601. printf("%s", i == 0 ? "" : COLUMN_SEP);
  602. for (j = 0; j < len; j++)
  603. printf("%c", HEADER_CHAR);
  604. }
  605. printf("\n");
  606. }
  607. static void output_headers(enum resfmt fmt)
  608. {
  609. int i, len;
  610. for (i = 0; i < env.output_spec.spec_cnt; i++) {
  611. int id = env.output_spec.ids[i];
  612. int *max_len = &env.output_spec.lens[i];
  613. switch (fmt) {
  614. case RESFMT_TABLE_CALCLEN:
  615. len = snprintf(NULL, 0, "%s", stat_defs[id].header);
  616. if (len > *max_len)
  617. *max_len = len;
  618. break;
  619. case RESFMT_TABLE:
  620. printf("%s%-*s", i == 0 ? "" : COLUMN_SEP, *max_len, stat_defs[id].header);
  621. if (i == env.output_spec.spec_cnt - 1)
  622. printf("\n");
  623. break;
  624. case RESFMT_CSV:
  625. printf("%s%s", i == 0 ? "" : ",", stat_defs[id].names[0]);
  626. if (i == env.output_spec.spec_cnt - 1)
  627. printf("\n");
  628. break;
  629. }
  630. }
  631. if (fmt == RESFMT_TABLE)
  632. output_header_underlines();
  633. }
  634. static void prepare_value(const struct verif_stats *s, enum stat_id id,
  635. const char **str, long *val)
  636. {
  637. switch (id) {
  638. case FILE_NAME:
  639. *str = s->file_name;
  640. break;
  641. case PROG_NAME:
  642. *str = s->prog_name;
  643. break;
  644. case VERDICT:
  645. *str = s->stats[VERDICT] ? "success" : "failure";
  646. break;
  647. case DURATION:
  648. case TOTAL_INSNS:
  649. case TOTAL_STATES:
  650. case PEAK_STATES:
  651. case MAX_STATES_PER_INSN:
  652. case MARK_READ_MAX_LEN:
  653. *val = s->stats[id];
  654. break;
  655. default:
  656. fprintf(stderr, "Unrecognized stat #%d\n", id);
  657. exit(1);
  658. }
  659. }
  660. static void output_stats(const struct verif_stats *s, enum resfmt fmt, bool last)
  661. {
  662. int i;
  663. for (i = 0; i < env.output_spec.spec_cnt; i++) {
  664. int id = env.output_spec.ids[i];
  665. int *max_len = &env.output_spec.lens[i], len;
  666. const char *str = NULL;
  667. long val = 0;
  668. prepare_value(s, id, &str, &val);
  669. switch (fmt) {
  670. case RESFMT_TABLE_CALCLEN:
  671. if (str)
  672. len = snprintf(NULL, 0, "%s", str);
  673. else
  674. len = snprintf(NULL, 0, "%ld", val);
  675. if (len > *max_len)
  676. *max_len = len;
  677. break;
  678. case RESFMT_TABLE:
  679. if (str)
  680. printf("%s%-*s", i == 0 ? "" : COLUMN_SEP, *max_len, str);
  681. else
  682. printf("%s%*ld", i == 0 ? "" : COLUMN_SEP, *max_len, val);
  683. if (i == env.output_spec.spec_cnt - 1)
  684. printf("\n");
  685. break;
  686. case RESFMT_CSV:
  687. if (str)
  688. printf("%s%s", i == 0 ? "" : ",", str);
  689. else
  690. printf("%s%ld", i == 0 ? "" : ",", val);
  691. if (i == env.output_spec.spec_cnt - 1)
  692. printf("\n");
  693. break;
  694. }
  695. }
  696. if (last && fmt == RESFMT_TABLE) {
  697. output_header_underlines();
  698. printf("Done. Processed %d files, %d programs. Skipped %d files, %d programs.\n",
  699. env.files_processed, env.files_skipped, env.progs_processed, env.progs_skipped);
  700. }
  701. }
  702. static int handle_verif_mode(void)
  703. {
  704. int i, err;
  705. if (env.filename_cnt == 0) {
  706. fprintf(stderr, "Please provide path to BPF object file!\n");
  707. argp_help(&argp, stderr, ARGP_HELP_USAGE, "veristat");
  708. return -EINVAL;
  709. }
  710. for (i = 0; i < env.filename_cnt; i++) {
  711. err = process_obj(env.filenames[i]);
  712. if (err) {
  713. fprintf(stderr, "Failed to process '%s': %d\n", env.filenames[i], err);
  714. return err;
  715. }
  716. }
  717. qsort(env.prog_stats, env.prog_stat_cnt, sizeof(*env.prog_stats), cmp_prog_stats);
  718. if (env.out_fmt == RESFMT_TABLE) {
  719. /* calculate column widths */
  720. output_headers(RESFMT_TABLE_CALCLEN);
  721. for (i = 0; i < env.prog_stat_cnt; i++)
  722. output_stats(&env.prog_stats[i], RESFMT_TABLE_CALCLEN, false);
  723. }
  724. /* actually output the table */
  725. output_headers(env.out_fmt);
  726. for (i = 0; i < env.prog_stat_cnt; i++) {
  727. output_stats(&env.prog_stats[i], env.out_fmt, i == env.prog_stat_cnt - 1);
  728. }
  729. return 0;
  730. }
  731. static int parse_stat_value(const char *str, enum stat_id id, struct verif_stats *st)
  732. {
  733. switch (id) {
  734. case FILE_NAME:
  735. st->file_name = strdup(str);
  736. if (!st->file_name)
  737. return -ENOMEM;
  738. break;
  739. case PROG_NAME:
  740. st->prog_name = strdup(str);
  741. if (!st->prog_name)
  742. return -ENOMEM;
  743. break;
  744. case VERDICT:
  745. if (strcmp(str, "success") == 0) {
  746. st->stats[VERDICT] = true;
  747. } else if (strcmp(str, "failure") == 0) {
  748. st->stats[VERDICT] = false;
  749. } else {
  750. fprintf(stderr, "Unrecognized verification verdict '%s'\n", str);
  751. return -EINVAL;
  752. }
  753. break;
  754. case DURATION:
  755. case TOTAL_INSNS:
  756. case TOTAL_STATES:
  757. case PEAK_STATES:
  758. case MAX_STATES_PER_INSN:
  759. case MARK_READ_MAX_LEN: {
  760. long val;
  761. int err, n;
  762. if (sscanf(str, "%ld %n", &val, &n) != 1 || n != strlen(str)) {
  763. err = -errno;
  764. fprintf(stderr, "Failed to parse '%s' as integer\n", str);
  765. return err;
  766. }
  767. st->stats[id] = val;
  768. break;
  769. }
  770. default:
  771. fprintf(stderr, "Unrecognized stat #%d\n", id);
  772. return -EINVAL;
  773. }
  774. return 0;
  775. }
  776. static int parse_stats_csv(const char *filename, struct stat_specs *specs,
  777. struct verif_stats **statsp, int *stat_cntp)
  778. {
  779. char line[4096];
  780. FILE *f;
  781. int err = 0;
  782. bool header = true;
  783. f = fopen(filename, "r");
  784. if (!f) {
  785. err = -errno;
  786. fprintf(stderr, "Failed to open '%s': %d\n", filename, err);
  787. return err;
  788. }
  789. *stat_cntp = 0;
  790. while (fgets(line, sizeof(line), f)) {
  791. char *input = line, *state = NULL, *next;
  792. struct verif_stats *st = NULL;
  793. int col = 0;
  794. if (!header) {
  795. void *tmp;
  796. tmp = realloc(*statsp, (*stat_cntp + 1) * sizeof(**statsp));
  797. if (!tmp) {
  798. err = -ENOMEM;
  799. goto cleanup;
  800. }
  801. *statsp = tmp;
  802. st = &(*statsp)[*stat_cntp];
  803. memset(st, 0, sizeof(*st));
  804. *stat_cntp += 1;
  805. }
  806. while ((next = strtok_r(state ? NULL : input, ",\n", &state))) {
  807. if (header) {
  808. /* for the first line, set up spec stats */
  809. err = parse_stat(next, specs);
  810. if (err)
  811. goto cleanup;
  812. continue;
  813. }
  814. /* for all other lines, parse values based on spec */
  815. if (col >= specs->spec_cnt) {
  816. fprintf(stderr, "Found extraneous column #%d in row #%d of '%s'\n",
  817. col, *stat_cntp, filename);
  818. err = -EINVAL;
  819. goto cleanup;
  820. }
  821. err = parse_stat_value(next, specs->ids[col], st);
  822. if (err)
  823. goto cleanup;
  824. col++;
  825. }
  826. if (header) {
  827. header = false;
  828. continue;
  829. }
  830. if (col < specs->spec_cnt) {
  831. fprintf(stderr, "Not enough columns in row #%d in '%s'\n",
  832. *stat_cntp, filename);
  833. err = -EINVAL;
  834. goto cleanup;
  835. }
  836. if (!st->file_name || !st->prog_name) {
  837. fprintf(stderr, "Row #%d in '%s' is missing file and/or program name\n",
  838. *stat_cntp, filename);
  839. err = -EINVAL;
  840. goto cleanup;
  841. }
  842. /* in comparison mode we can only check filters after we
  843. * parsed entire line; if row should be ignored we pretend we
  844. * never parsed it
  845. */
  846. if (!should_process_prog(st->file_name, st->prog_name)) {
  847. free(st->file_name);
  848. free(st->prog_name);
  849. *stat_cntp -= 1;
  850. }
  851. }
  852. if (!feof(f)) {
  853. err = -errno;
  854. fprintf(stderr, "Failed I/O for '%s': %d\n", filename, err);
  855. }
  856. cleanup:
  857. fclose(f);
  858. return err;
  859. }
  860. /* empty/zero stats for mismatched rows */
  861. static const struct verif_stats fallback_stats = { .file_name = "", .prog_name = "" };
  862. static bool is_key_stat(enum stat_id id)
  863. {
  864. return id == FILE_NAME || id == PROG_NAME;
  865. }
  866. static void output_comp_header_underlines(void)
  867. {
  868. int i, j, k;
  869. for (i = 0; i < env.output_spec.spec_cnt; i++) {
  870. int id = env.output_spec.ids[i];
  871. int max_j = is_key_stat(id) ? 1 : 3;
  872. for (j = 0; j < max_j; j++) {
  873. int len = env.output_spec.lens[3 * i + j];
  874. printf("%s", i + j == 0 ? "" : COLUMN_SEP);
  875. for (k = 0; k < len; k++)
  876. printf("%c", HEADER_CHAR);
  877. }
  878. }
  879. printf("\n");
  880. }
  881. static void output_comp_headers(enum resfmt fmt)
  882. {
  883. static const char *table_sfxs[3] = {" (A)", " (B)", " (DIFF)"};
  884. static const char *name_sfxs[3] = {"_base", "_comp", "_diff"};
  885. int i, j, len;
  886. for (i = 0; i < env.output_spec.spec_cnt; i++) {
  887. int id = env.output_spec.ids[i];
  888. /* key stats don't have A/B/DIFF columns, they are common for both data sets */
  889. int max_j = is_key_stat(id) ? 1 : 3;
  890. for (j = 0; j < max_j; j++) {
  891. int *max_len = &env.output_spec.lens[3 * i + j];
  892. bool last = (i == env.output_spec.spec_cnt - 1) && (j == max_j - 1);
  893. const char *sfx;
  894. switch (fmt) {
  895. case RESFMT_TABLE_CALCLEN:
  896. sfx = is_key_stat(id) ? "" : table_sfxs[j];
  897. len = snprintf(NULL, 0, "%s%s", stat_defs[id].header, sfx);
  898. if (len > *max_len)
  899. *max_len = len;
  900. break;
  901. case RESFMT_TABLE:
  902. sfx = is_key_stat(id) ? "" : table_sfxs[j];
  903. printf("%s%-*s%s", i + j == 0 ? "" : COLUMN_SEP,
  904. *max_len - (int)strlen(sfx), stat_defs[id].header, sfx);
  905. if (last)
  906. printf("\n");
  907. break;
  908. case RESFMT_CSV:
  909. sfx = is_key_stat(id) ? "" : name_sfxs[j];
  910. printf("%s%s%s", i + j == 0 ? "" : ",", stat_defs[id].names[0], sfx);
  911. if (last)
  912. printf("\n");
  913. break;
  914. }
  915. }
  916. }
  917. if (fmt == RESFMT_TABLE)
  918. output_comp_header_underlines();
  919. }
  920. static void output_comp_stats(const struct verif_stats *base, const struct verif_stats *comp,
  921. enum resfmt fmt, bool last)
  922. {
  923. char base_buf[1024] = {}, comp_buf[1024] = {}, diff_buf[1024] = {};
  924. int i;
  925. for (i = 0; i < env.output_spec.spec_cnt; i++) {
  926. int id = env.output_spec.ids[i], len;
  927. int *max_len_base = &env.output_spec.lens[3 * i + 0];
  928. int *max_len_comp = &env.output_spec.lens[3 * i + 1];
  929. int *max_len_diff = &env.output_spec.lens[3 * i + 2];
  930. const char *base_str = NULL, *comp_str = NULL;
  931. long base_val = 0, comp_val = 0, diff_val = 0;
  932. prepare_value(base, id, &base_str, &base_val);
  933. prepare_value(comp, id, &comp_str, &comp_val);
  934. /* normalize all the outputs to be in string buffers for simplicity */
  935. if (is_key_stat(id)) {
  936. /* key stats (file and program name) are always strings */
  937. if (base != &fallback_stats)
  938. snprintf(base_buf, sizeof(base_buf), "%s", base_str);
  939. else
  940. snprintf(base_buf, sizeof(base_buf), "%s", comp_str);
  941. } else if (base_str) {
  942. snprintf(base_buf, sizeof(base_buf), "%s", base_str);
  943. snprintf(comp_buf, sizeof(comp_buf), "%s", comp_str);
  944. if (strcmp(base_str, comp_str) == 0)
  945. snprintf(diff_buf, sizeof(diff_buf), "%s", "MATCH");
  946. else
  947. snprintf(diff_buf, sizeof(diff_buf), "%s", "MISMATCH");
  948. } else {
  949. snprintf(base_buf, sizeof(base_buf), "%ld", base_val);
  950. snprintf(comp_buf, sizeof(comp_buf), "%ld", comp_val);
  951. diff_val = comp_val - base_val;
  952. if (base == &fallback_stats || comp == &fallback_stats || base_val == 0) {
  953. snprintf(diff_buf, sizeof(diff_buf), "%+ld (%+.2lf%%)",
  954. diff_val, comp_val < base_val ? -100.0 : 100.0);
  955. } else {
  956. snprintf(diff_buf, sizeof(diff_buf), "%+ld (%+.2lf%%)",
  957. diff_val, diff_val * 100.0 / base_val);
  958. }
  959. }
  960. switch (fmt) {
  961. case RESFMT_TABLE_CALCLEN:
  962. len = strlen(base_buf);
  963. if (len > *max_len_base)
  964. *max_len_base = len;
  965. if (!is_key_stat(id)) {
  966. len = strlen(comp_buf);
  967. if (len > *max_len_comp)
  968. *max_len_comp = len;
  969. len = strlen(diff_buf);
  970. if (len > *max_len_diff)
  971. *max_len_diff = len;
  972. }
  973. break;
  974. case RESFMT_TABLE: {
  975. /* string outputs are left-aligned, number outputs are right-aligned */
  976. const char *fmt = base_str ? "%s%-*s" : "%s%*s";
  977. printf(fmt, i == 0 ? "" : COLUMN_SEP, *max_len_base, base_buf);
  978. if (!is_key_stat(id)) {
  979. printf(fmt, COLUMN_SEP, *max_len_comp, comp_buf);
  980. printf(fmt, COLUMN_SEP, *max_len_diff, diff_buf);
  981. }
  982. if (i == env.output_spec.spec_cnt - 1)
  983. printf("\n");
  984. break;
  985. }
  986. case RESFMT_CSV:
  987. printf("%s%s", i == 0 ? "" : ",", base_buf);
  988. if (!is_key_stat(id)) {
  989. printf("%s%s", i == 0 ? "" : ",", comp_buf);
  990. printf("%s%s", i == 0 ? "" : ",", diff_buf);
  991. }
  992. if (i == env.output_spec.spec_cnt - 1)
  993. printf("\n");
  994. break;
  995. }
  996. }
  997. if (last && fmt == RESFMT_TABLE)
  998. output_comp_header_underlines();
  999. }
  1000. static int cmp_stats_key(const struct verif_stats *base, const struct verif_stats *comp)
  1001. {
  1002. int r;
  1003. r = strcmp(base->file_name, comp->file_name);
  1004. if (r != 0)
  1005. return r;
  1006. return strcmp(base->prog_name, comp->prog_name);
  1007. }
  1008. static int handle_comparison_mode(void)
  1009. {
  1010. struct stat_specs base_specs = {}, comp_specs = {};
  1011. enum resfmt cur_fmt;
  1012. int err, i, j;
  1013. if (env.filename_cnt != 2) {
  1014. fprintf(stderr, "Comparison mode expects exactly two input CSV files!\n");
  1015. argp_help(&argp, stderr, ARGP_HELP_USAGE, "veristat");
  1016. return -EINVAL;
  1017. }
  1018. err = parse_stats_csv(env.filenames[0], &base_specs,
  1019. &env.baseline_stats, &env.baseline_stat_cnt);
  1020. if (err) {
  1021. fprintf(stderr, "Failed to parse stats from '%s': %d\n", env.filenames[0], err);
  1022. return err;
  1023. }
  1024. err = parse_stats_csv(env.filenames[1], &comp_specs,
  1025. &env.prog_stats, &env.prog_stat_cnt);
  1026. if (err) {
  1027. fprintf(stderr, "Failed to parse stats from '%s': %d\n", env.filenames[1], err);
  1028. return err;
  1029. }
  1030. /* To keep it simple we validate that the set and order of stats in
  1031. * both CSVs are exactly the same. This can be lifted with a bit more
  1032. * pre-processing later.
  1033. */
  1034. if (base_specs.spec_cnt != comp_specs.spec_cnt) {
  1035. fprintf(stderr, "Number of stats in '%s' and '%s' differs (%d != %d)!\n",
  1036. env.filenames[0], env.filenames[1],
  1037. base_specs.spec_cnt, comp_specs.spec_cnt);
  1038. return -EINVAL;
  1039. }
  1040. for (i = 0; i < base_specs.spec_cnt; i++) {
  1041. if (base_specs.ids[i] != comp_specs.ids[i]) {
  1042. fprintf(stderr, "Stats composition differs between '%s' and '%s' (%s != %s)!\n",
  1043. env.filenames[0], env.filenames[1],
  1044. stat_defs[base_specs.ids[i]].names[0],
  1045. stat_defs[comp_specs.ids[i]].names[0]);
  1046. return -EINVAL;
  1047. }
  1048. }
  1049. qsort(env.prog_stats, env.prog_stat_cnt, sizeof(*env.prog_stats), cmp_prog_stats);
  1050. qsort(env.baseline_stats, env.baseline_stat_cnt, sizeof(*env.baseline_stats), cmp_prog_stats);
  1051. /* for human-readable table output we need to do extra pass to
  1052. * calculate column widths, so we substitute current output format
  1053. * with RESFMT_TABLE_CALCLEN and later revert it back to RESFMT_TABLE
  1054. * and do everything again.
  1055. */
  1056. if (env.out_fmt == RESFMT_TABLE)
  1057. cur_fmt = RESFMT_TABLE_CALCLEN;
  1058. else
  1059. cur_fmt = env.out_fmt;
  1060. one_more_time:
  1061. output_comp_headers(cur_fmt);
  1062. /* If baseline and comparison datasets have different subset of rows
  1063. * (we match by 'object + prog' as a unique key) then assume
  1064. * empty/missing/zero value for rows that are missing in the opposite
  1065. * data set
  1066. */
  1067. i = j = 0;
  1068. while (i < env.baseline_stat_cnt || j < env.prog_stat_cnt) {
  1069. bool last = (i == env.baseline_stat_cnt - 1) || (j == env.prog_stat_cnt - 1);
  1070. const struct verif_stats *base, *comp;
  1071. int r;
  1072. base = i < env.baseline_stat_cnt ? &env.baseline_stats[i] : &fallback_stats;
  1073. comp = j < env.prog_stat_cnt ? &env.prog_stats[j] : &fallback_stats;
  1074. if (!base->file_name || !base->prog_name) {
  1075. fprintf(stderr, "Entry #%d in '%s' doesn't have file and/or program name specified!\n",
  1076. i, env.filenames[0]);
  1077. return -EINVAL;
  1078. }
  1079. if (!comp->file_name || !comp->prog_name) {
  1080. fprintf(stderr, "Entry #%d in '%s' doesn't have file and/or program name specified!\n",
  1081. j, env.filenames[1]);
  1082. return -EINVAL;
  1083. }
  1084. r = cmp_stats_key(base, comp);
  1085. if (r == 0) {
  1086. output_comp_stats(base, comp, cur_fmt, last);
  1087. i++;
  1088. j++;
  1089. } else if (comp == &fallback_stats || r < 0) {
  1090. output_comp_stats(base, &fallback_stats, cur_fmt, last);
  1091. i++;
  1092. } else {
  1093. output_comp_stats(&fallback_stats, comp, cur_fmt, last);
  1094. j++;
  1095. }
  1096. }
  1097. if (cur_fmt == RESFMT_TABLE_CALCLEN) {
  1098. cur_fmt = RESFMT_TABLE;
  1099. goto one_more_time; /* ... this time with feeling */
  1100. }
  1101. return 0;
  1102. }
  1103. int main(int argc, char **argv)
  1104. {
  1105. int err = 0, i;
  1106. if (argp_parse(&argp, argc, argv, 0, NULL, NULL))
  1107. return 1;
  1108. if (env.verbose && env.quiet) {
  1109. fprintf(stderr, "Verbose and quiet modes are incompatible, please specify just one or neither!\n");
  1110. argp_help(&argp, stderr, ARGP_HELP_USAGE, "veristat");
  1111. return 1;
  1112. }
  1113. if (env.verbose && env.log_level == 0)
  1114. env.log_level = 1;
  1115. if (env.output_spec.spec_cnt == 0)
  1116. env.output_spec = default_output_spec;
  1117. if (env.sort_spec.spec_cnt == 0)
  1118. env.sort_spec = default_sort_spec;
  1119. if (env.comparison_mode)
  1120. err = handle_comparison_mode();
  1121. else
  1122. err = handle_verif_mode();
  1123. free_verif_stats(env.prog_stats, env.prog_stat_cnt);
  1124. free_verif_stats(env.baseline_stats, env.baseline_stat_cnt);
  1125. for (i = 0; i < env.filename_cnt; i++)
  1126. free(env.filenames[i]);
  1127. free(env.filenames);
  1128. for (i = 0; i < env.allow_filter_cnt; i++) {
  1129. free(env.allow_filters[i].file_glob);
  1130. free(env.allow_filters[i].prog_glob);
  1131. }
  1132. free(env.allow_filters);
  1133. for (i = 0; i < env.deny_filter_cnt; i++) {
  1134. free(env.deny_filters[i].file_glob);
  1135. free(env.deny_filters[i].prog_glob);
  1136. }
  1137. free(env.deny_filters);
  1138. return -err;
  1139. }