1 #include "settings.h"
2
3 #include <ctype.h>
4 #include <errno.h>
5 #include <fcntl.h>
6 #include <getopt.h>
7 #include <libgen.h>
8 #include <limits.h>
9 #include <stdio.h>
10 #include <stdlib.h>
11 #include <string.h>
12 #include <sys/types.h>
13 #include <sys/stat.h>
14 #include <unistd.h>
15
16 enum {
17 OPT_ABORT_ON_ERROR,
18 OPT_TEST_LIST,
19 OPT_IGNORE_MISSING,
20 OPT_PIGLIT_DMESG,
21 OPT_DMESG_WARN_LEVEL,
22 OPT_OVERALL_TIMEOUT,
23 OPT_HELP = 'h',
24 OPT_NAME = 'n',
25 OPT_DRY_RUN = 'd',
26 OPT_INCLUDE = 't',
27 OPT_EXCLUDE = 'x',
28 OPT_SYNC = 's',
29 OPT_LOG_LEVEL = 'l',
30 OPT_OVERWRITE = 'o',
31 OPT_MULTIPLE = 'm',
32 OPT_TIMEOUT = 'c',
33 OPT_WATCHDOG = 'g',
34 OPT_BLACKLIST = 'b',
35 OPT_LIST_ALL = 'L',
36 };
37
38 static struct {
39 int level;
40 const char *name;
41 } log_levels[] = {
42 { LOG_LEVEL_NORMAL, "normal" },
43 { LOG_LEVEL_QUIET, "quiet" },
44 { LOG_LEVEL_VERBOSE, "verbose" },
45 { 0, 0 },
46 };
47
48 static struct {
49 int value;
50 const char *name;
51 } abort_conditions[] = {
52 { ABORT_TAINT, "taint" },
53 { ABORT_LOCKDEP, "lockdep" },
54 { ABORT_ALL, "all" },
55 { 0, 0 },
56 };
57
set_log_level(struct settings * settings,const char * level)58 static bool set_log_level(struct settings* settings, const char *level)
59 {
60 typeof(*log_levels) *it;
61
62 for (it = log_levels; it->name; it++) {
63 if (!strcmp(level, it->name)) {
64 settings->log_level = it->level;
65 return true;
66 }
67 }
68
69 return false;
70 }
71
set_abort_condition(struct settings * settings,const char * cond)72 static bool set_abort_condition(struct settings* settings, const char *cond)
73 {
74 typeof(*abort_conditions) *it;
75
76 if (!cond) {
77 settings->abort_mask = ABORT_ALL;
78 return true;
79 }
80
81 if (strlen(cond) == 0) {
82 settings->abort_mask = 0;
83 return true;
84 }
85
86 for (it = abort_conditions; it->name; it++) {
87 if (!strcmp(cond, it->name)) {
88 settings->abort_mask |= it->value;
89 return true;
90 }
91 }
92
93 return false;
94 }
95
parse_abort_conditions(struct settings * settings,const char * optarg)96 static bool parse_abort_conditions(struct settings *settings, const char *optarg)
97 {
98 char *dup, *origdup, *p;
99 if (!optarg)
100 return set_abort_condition(settings, NULL);
101
102 origdup = dup = strdup(optarg);
103 while (dup) {
104 if ((p = strchr(dup, ',')) != NULL) {
105 *p = '\0';
106 p++;
107 }
108
109 if (!set_abort_condition(settings, dup)) {
110 free(origdup);
111 return false;
112 }
113
114 dup = p;
115 }
116
117 free(origdup);
118
119 return true;
120 }
121
122 static const char *usage_str =
123 "usage: runner [options] [test_root] results-path\n"
124 " or: runner --list-all [options] [test_root]\n\n"
125 "Options:\n"
126 " Piglit compatible:\n"
127 " -h, --help Show this help message and exit\n"
128 " -n <test name>, --name <test name>\n"
129 " Name of this test run\n"
130 " -d, --dry-run Do not execute the tests\n"
131 " -t <regex>, --include-tests <regex>\n"
132 " Run only matching tests (can be used more than once)\n"
133 " -x <regex>, --exclude-tests <regex>\n"
134 " Exclude matching tests (can be used more than once)\n"
135 " --abort-on-monitored-error[=list]\n"
136 " Abort execution when a fatal condition is detected.\n"
137 " A comma-separated list of conditions to check can be\n"
138 " given. If not given, all conditions are checked. An\n"
139 " empty string as a condition disables aborting\n"
140 " Possible conditions:\n"
141 " lockdep - abort when kernel lockdep has been angered.\n"
142 " taint - abort when kernel becomes fatally tainted.\n"
143 " all - abort for all of the above.\n"
144 " -s, --sync Sync results to disk after every test\n"
145 " -l {quiet,verbose,dummy}, --log-level {quiet,verbose,dummy}\n"
146 " Set the logger verbosity level\n"
147 " --test-list TEST_LIST\n"
148 " A file containing a list of tests to run\n"
149 " -o, --overwrite If the results-path already exists, delete it\n"
150 " --ignore-missing Ignored but accepted, for piglit compatibility\n"
151 "\n"
152 " Incompatible options:\n"
153 " -m, --multiple-mode Run multiple subtests in the same binary execution.\n"
154 " If a testlist file is given, consecutive subtests are\n"
155 " run in the same execution if they are from the same\n"
156 " binary. Note that in that case relative ordering of the\n"
157 " subtest execution is dictated by the test binary, not\n"
158 " the testlist\n"
159 " --inactivity-timeout <seconds>\n"
160 " Kill the running test after <seconds> of inactivity in\n"
161 " the test's stdout, stderr, or dmesg\n"
162 " --overall-timeout <seconds>\n"
163 " Don't execute more tests after <seconds> has elapsed\n"
164 " --use-watchdog Use hardware watchdog for lethal enforcement of the\n"
165 " above timeout. Killing the test process is still\n"
166 " attempted at timeout trigger.\n"
167 " --dmesg-warn-level <level>\n"
168 " Messages with log level equal or lower (more serious)\n"
169 " to the given one will override the test result to\n"
170 " dmesg-warn/dmesg-fail, assuming they go through filtering.\n"
171 " Defaults to 4 (KERN_WARNING).\n"
172 " --piglit-style-dmesg Filter dmesg like piglit does. Piglit considers matches\n"
173 " against a short filter list to mean the test result\n"
174 " should be changed to dmesg-warn/dmesg-fail. Without\n"
175 " this option everything except matches against a\n"
176 " (longer) filter list means the test result should\n"
177 " change. KERN_NOTICE dmesg level is treated as warn,\n"
178 " unless overridden with --dmesg-warn-level.\n"
179 " -b, --blacklist FILENAME\n"
180 " Exclude all test matching to regexes from FILENAME\n"
181 " (can be used more than once)\n"
182 " -L, --list-all List all matching subtests instead of running\n"
183 " [test_root] Directory that contains the IGT tests. The environment\n"
184 " variable IGT_TEST_ROOT will be used if set, overriding\n"
185 " this option if given.\n"
186 ;
187
usage(const char * extra_message,FILE * f)188 static void usage(const char *extra_message, FILE *f)
189 {
190 if (extra_message)
191 fprintf(f, "%s\n\n", extra_message);
192
193 fputs(usage_str, f);
194 }
195
add_regex(struct regex_list * list,char * new)196 static bool add_regex(struct regex_list *list, char *new)
197 {
198 GRegex *regex;
199 GError *error = NULL;
200
201 regex = g_regex_new(new, G_REGEX_OPTIMIZE, 0, &error);
202 if (error) {
203 char *buf = malloc(snprintf(NULL, 0, "Invalid regex '%s': %s", new, error->message) + 1);
204
205 sprintf(buf, "Invalid regex '%s': %s", new, error->message);
206 usage(buf, stderr);
207
208 free(buf);
209 g_error_free(error);
210 return false;
211 }
212
213 list->regexes = realloc(list->regexes,
214 (list->size + 1) * sizeof(*list->regexes));
215 list->regex_strings = realloc(list->regex_strings,
216 (list->size + 1) * sizeof(*list->regex_strings));
217 list->regexes[list->size] = regex;
218 list->regex_strings[list->size] = new;
219 list->size++;
220
221 return true;
222 }
223
parse_blacklist(struct regex_list * exclude_regexes,char * blacklist_filename)224 static bool parse_blacklist(struct regex_list *exclude_regexes,
225 char *blacklist_filename)
226 {
227 FILE *f;
228 char *line = NULL;
229 size_t line_len = 0;
230 bool status = false;
231
232 if ((f = fopen(blacklist_filename, "r")) == NULL) {
233 fprintf(stderr, "Cannot open blacklist file %s\n", blacklist_filename);
234 return false;
235 }
236 while (1) {
237 size_t str_size = 0, idx = 0;
238
239 if (getline(&line, &line_len, f) == -1) {
240 if (errno == EINTR)
241 continue;
242 else
243 break;
244 }
245
246 while (true) {
247 if (line[idx] == '\n' ||
248 line[idx] == '\0' ||
249 line[idx] == '#') /* # starts a comment */
250 break;
251 if (!isspace(line[idx]))
252 str_size = idx + 1;
253 idx++;
254 }
255 if (str_size > 0) {
256 char *test_regex = strndup(line, str_size);
257
258 status = add_regex(exclude_regexes, test_regex);
259 if (!status)
260 break;
261 }
262 }
263
264 free(line);
265 fclose(f);
266 return status;
267 }
268
free_regexes(struct regex_list * regexes)269 static void free_regexes(struct regex_list *regexes)
270 {
271 size_t i;
272
273 for (i = 0; i < regexes->size; i++) {
274 free(regexes->regex_strings[i]);
275 g_regex_unref(regexes->regexes[i]);
276 }
277 free(regexes->regex_strings);
278 free(regexes->regexes);
279 }
280
readable_file(char * filename)281 static bool readable_file(char *filename)
282 {
283 return !access(filename, R_OK);
284 }
285
init_settings(struct settings * settings)286 void init_settings(struct settings *settings)
287 {
288 memset(settings, 0, sizeof(*settings));
289 }
290
free_settings(struct settings * settings)291 void free_settings(struct settings *settings)
292 {
293 free(settings->test_list);
294 free(settings->name);
295 free(settings->test_root);
296 free(settings->results_path);
297
298 free_regexes(&settings->include_regexes);
299 free_regexes(&settings->exclude_regexes);
300
301 init_settings(settings);
302 }
303
parse_options(int argc,char ** argv,struct settings * settings)304 bool parse_options(int argc, char **argv,
305 struct settings *settings)
306 {
307 int c;
308 char *env_test_root;
309
310 static struct option long_options[] = {
311 {"help", no_argument, NULL, OPT_HELP},
312 {"name", required_argument, NULL, OPT_NAME},
313 {"dry-run", no_argument, NULL, OPT_DRY_RUN},
314 {"include-tests", required_argument, NULL, OPT_INCLUDE},
315 {"exclude-tests", required_argument, NULL, OPT_EXCLUDE},
316 {"abort-on-monitored-error", optional_argument, NULL, OPT_ABORT_ON_ERROR},
317 {"sync", no_argument, NULL, OPT_SYNC},
318 {"log-level", required_argument, NULL, OPT_LOG_LEVEL},
319 {"test-list", required_argument, NULL, OPT_TEST_LIST},
320 {"overwrite", no_argument, NULL, OPT_OVERWRITE},
321 {"ignore-missing", no_argument, NULL, OPT_IGNORE_MISSING},
322 {"multiple-mode", no_argument, NULL, OPT_MULTIPLE},
323 {"inactivity-timeout", required_argument, NULL, OPT_TIMEOUT},
324 {"overall-timeout", required_argument, NULL, OPT_OVERALL_TIMEOUT},
325 {"use-watchdog", no_argument, NULL, OPT_WATCHDOG},
326 {"piglit-style-dmesg", no_argument, NULL, OPT_PIGLIT_DMESG},
327 {"dmesg-warn-level", required_argument, NULL, OPT_DMESG_WARN_LEVEL},
328 {"blacklist", required_argument, NULL, OPT_BLACKLIST},
329 {"list-all", no_argument, NULL, OPT_LIST_ALL},
330 { 0, 0, 0, 0},
331 };
332
333 free_settings(settings);
334
335 optind = 1;
336
337 settings->dmesg_warn_level = -1;
338
339 while ((c = getopt_long(argc, argv, "hn:dt:x:sl:omb:L",
340 long_options, NULL)) != -1) {
341 switch (c) {
342 case OPT_HELP:
343 usage(NULL, stdout);
344 goto error;
345 case OPT_NAME:
346 settings->name = strdup(optarg);
347 break;
348 case OPT_DRY_RUN:
349 settings->dry_run = true;
350 break;
351 case OPT_INCLUDE:
352 if (!add_regex(&settings->include_regexes, strdup(optarg)))
353 goto error;
354 break;
355 case OPT_EXCLUDE:
356 if (!add_regex(&settings->exclude_regexes, strdup(optarg)))
357 goto error;
358 break;
359 case OPT_ABORT_ON_ERROR:
360 if (!parse_abort_conditions(settings, optarg))
361 goto error;
362 break;
363 case OPT_SYNC:
364 settings->sync = true;
365 break;
366 case OPT_LOG_LEVEL:
367 if (!set_log_level(settings, optarg)) {
368 usage("Cannot parse log level", stderr);
369 goto error;
370 }
371 break;
372 case OPT_TEST_LIST:
373 settings->test_list = absolute_path(optarg);
374 break;
375 case OPT_OVERWRITE:
376 settings->overwrite = true;
377 break;
378 case OPT_IGNORE_MISSING:
379 /* Ignored, piglit compatibility */
380 break;
381 case OPT_MULTIPLE:
382 settings->multiple_mode = true;
383 break;
384 case OPT_TIMEOUT:
385 settings->inactivity_timeout = atoi(optarg);
386 break;
387 case OPT_OVERALL_TIMEOUT:
388 settings->overall_timeout = atoi(optarg);
389 break;
390 case OPT_WATCHDOG:
391 settings->use_watchdog = true;
392 break;
393 case OPT_PIGLIT_DMESG:
394 settings->piglit_style_dmesg = true;
395 if (settings->dmesg_warn_level < 0)
396 settings->dmesg_warn_level = 5; /* KERN_NOTICE */
397 break;
398 case OPT_DMESG_WARN_LEVEL:
399 settings->dmesg_warn_level = atoi(optarg);
400 break;
401 case OPT_BLACKLIST:
402 if (!parse_blacklist(&settings->exclude_regexes,
403 absolute_path(optarg)))
404 goto error;
405 break;
406 case OPT_LIST_ALL:
407 settings->list_all = true;
408 break;
409 case '?':
410 usage(NULL, stderr);
411 goto error;
412 default:
413 usage("Cannot parse options", stderr);
414 goto error;
415 }
416 }
417
418 if (settings->dmesg_warn_level < 0)
419 settings->dmesg_warn_level = 4; /* KERN_WARN */
420
421 if (settings->list_all) { /* --list-all doesn't require results path */
422 switch (argc - optind) {
423 case 1:
424 settings->test_root = absolute_path(argv[optind]);
425 ++optind;
426 /* fallthrough */
427 case 0:
428 break;
429 default:
430 usage("Too many arguments for --list-all", stderr);
431 goto error;
432 }
433 } else {
434 switch (argc - optind) {
435 case 2:
436 settings->test_root = absolute_path(argv[optind]);
437 ++optind;
438 /* fallthrough */
439 case 1:
440 settings->results_path = absolute_path(argv[optind]);
441 break;
442 case 0:
443 usage("Results-path missing", stderr);
444 goto error;
445 default:
446 usage("Extra arguments after results-path", stderr);
447 goto error;
448 }
449 if (!settings->name) {
450 char *name = strdup(settings->results_path);
451
452 settings->name = strdup(basename(name));
453 free(name);
454 }
455 }
456
457 if ((env_test_root = getenv("IGT_TEST_ROOT")) != NULL) {
458 free(settings->test_root);
459 settings->test_root = absolute_path(env_test_root);
460 }
461
462 if (!settings->test_root) {
463 usage("Test root not set", stderr);
464 goto error;
465 }
466
467
468 return true;
469
470 error:
471 free_settings(settings);
472 return false;
473 }
474
validate_settings(struct settings * settings)475 bool validate_settings(struct settings *settings)
476 {
477 int dirfd, fd;
478
479 if (settings->test_list && !readable_file(settings->test_list)) {
480 usage("Cannot open test-list file", stderr);
481 return false;
482 }
483
484 if (!settings->results_path) {
485 usage("No results-path set; this shouldn't happen", stderr);
486 return false;
487 }
488
489 if (!settings->test_root) {
490 usage("No test root set; this shouldn't happen", stderr);
491 return false;
492 }
493
494 dirfd = open(settings->test_root, O_DIRECTORY | O_RDONLY);
495 if (dirfd < 0) {
496 fprintf(stderr, "Test directory %s cannot be opened\n", settings->test_root);
497 return false;
498 }
499
500 fd = openat(dirfd, "test-list.txt", O_RDONLY);
501 if (fd < 0) {
502 fprintf(stderr, "Cannot open %s/test-list.txt\n", settings->test_root);
503 close(dirfd);
504 return false;
505 }
506
507 close(fd);
508 close(dirfd);
509
510 return true;
511 }
512
_dirname(const char * path)513 static char *_dirname(const char *path)
514 {
515 char *tmppath = strdup(path);
516 char *tmpname = dirname(tmppath);
517 tmpname = strdup(tmpname);
518 free(tmppath);
519 return tmpname;
520 }
521
_basename(const char * path)522 static char *_basename(const char *path)
523 {
524 char *tmppath = strdup(path);
525 char *tmpname = basename(tmppath);
526 tmpname = strdup(tmpname);
527 free(tmppath);
528 return tmpname;
529 }
530
absolute_path(char * path)531 char *absolute_path(char *path)
532 {
533 char *result = NULL;
534 char *base, *dir;
535 char *ret;
536
537 result = realpath(path, NULL);
538 if (result != NULL)
539 return result;
540
541 dir = _dirname(path);
542 ret = absolute_path(dir);
543 free(dir);
544
545 base = _basename(path);
546 asprintf(&result, "%s/%s", ret, base);
547 free(base);
548 free(ret);
549
550 return result;
551 }
552
553 static char settings_filename[] = "metadata.txt";
serialize_settings(struct settings * settings)554 bool serialize_settings(struct settings *settings)
555 {
556 #define SERIALIZE_LINE(f, s, name, format) fprintf(f, "%s : " format "\n", #name, s->name)
557
558 int dirfd, fd;
559 FILE *f;
560
561 if (!settings->results_path) {
562 usage("No results-path set; this shouldn't happen", stderr);
563 return false;
564 }
565
566 if ((dirfd = open(settings->results_path, O_DIRECTORY | O_RDONLY)) < 0) {
567 mkdir(settings->results_path, 0755);
568 if ((dirfd = open(settings->results_path, O_DIRECTORY | O_RDONLY)) < 0) {
569 usage("Creating results-path failed", stderr);
570 return false;
571 }
572 }
573
574 if (!settings->overwrite &&
575 faccessat(dirfd, settings_filename, F_OK, 0) == 0) {
576 usage("Settings metadata already exists and not overwriting", stderr);
577 return false;
578 }
579
580 if (settings->overwrite &&
581 unlinkat(dirfd, settings_filename, 0) != 0 &&
582 errno != ENOENT) {
583 usage("Error removing old settings metadata", stderr);
584 return false;
585 }
586
587 if ((fd = openat(dirfd, settings_filename, O_CREAT | O_EXCL | O_WRONLY, 0666)) < 0) {
588 char *msg;
589
590 asprintf(&msg, "Creating settings serialization file failed: %s", strerror(errno));
591 usage(msg, stderr);
592
593 free(msg);
594 close(dirfd);
595 return false;
596 }
597
598 f = fdopen(fd, "w");
599 if (!f) {
600 close(fd);
601 close(dirfd);
602 return false;
603 }
604
605 SERIALIZE_LINE(f, settings, abort_mask, "%d");
606 if (settings->test_list)
607 SERIALIZE_LINE(f, settings, test_list, "%s");
608 if (settings->name)
609 SERIALIZE_LINE(f, settings, name, "%s");
610 SERIALIZE_LINE(f, settings, dry_run, "%d");
611 SERIALIZE_LINE(f, settings, sync, "%d");
612 SERIALIZE_LINE(f, settings, log_level, "%d");
613 SERIALIZE_LINE(f, settings, overwrite, "%d");
614 SERIALIZE_LINE(f, settings, multiple_mode, "%d");
615 SERIALIZE_LINE(f, settings, inactivity_timeout, "%d");
616 SERIALIZE_LINE(f, settings, overall_timeout, "%d");
617 SERIALIZE_LINE(f, settings, use_watchdog, "%d");
618 SERIALIZE_LINE(f, settings, piglit_style_dmesg, "%d");
619 SERIALIZE_LINE(f, settings, dmesg_warn_level, "%d");
620 SERIALIZE_LINE(f, settings, test_root, "%s");
621 SERIALIZE_LINE(f, settings, results_path, "%s");
622
623 if (settings->sync) {
624 fsync(fd);
625 fsync(dirfd);
626 }
627
628 fclose(f);
629 close(dirfd);
630 return true;
631
632 #undef SERIALIZE_LINE
633 }
634
read_settings_from_file(struct settings * settings,FILE * f)635 bool read_settings_from_file(struct settings *settings, FILE *f)
636 {
637 #define PARSE_LINE(s, name, val, field, write) \
638 if (!strcmp(name, #field)) { \
639 s->field = write; \
640 free(name); \
641 free(val); \
642 name = val = NULL; \
643 continue; \
644 }
645
646 char *name = NULL, *val = NULL;
647
648 settings->dmesg_warn_level = -1;
649
650 while (fscanf(f, "%ms : %ms", &name, &val) == 2) {
651 int numval = atoi(val);
652 PARSE_LINE(settings, name, val, abort_mask, numval);
653 PARSE_LINE(settings, name, val, test_list, val ? strdup(val) : NULL);
654 PARSE_LINE(settings, name, val, name, val ? strdup(val) : NULL);
655 PARSE_LINE(settings, name, val, dry_run, numval);
656 PARSE_LINE(settings, name, val, sync, numval);
657 PARSE_LINE(settings, name, val, log_level, numval);
658 PARSE_LINE(settings, name, val, overwrite, numval);
659 PARSE_LINE(settings, name, val, multiple_mode, numval);
660 PARSE_LINE(settings, name, val, inactivity_timeout, numval);
661 PARSE_LINE(settings, name, val, overall_timeout, numval);
662 PARSE_LINE(settings, name, val, use_watchdog, numval);
663 PARSE_LINE(settings, name, val, piglit_style_dmesg, numval);
664 PARSE_LINE(settings, name, val, dmesg_warn_level, numval);
665 PARSE_LINE(settings, name, val, test_root, val ? strdup(val) : NULL);
666 PARSE_LINE(settings, name, val, results_path, val ? strdup(val) : NULL);
667
668 printf("Warning: Unknown field in settings file: %s = %s\n",
669 name, val);
670 free(name);
671 free(val);
672 name = val = NULL;
673 }
674
675 if (settings->dmesg_warn_level < 0) {
676 if (settings->piglit_style_dmesg)
677 settings->dmesg_warn_level = 5;
678 else
679 settings->dmesg_warn_level = 4;
680 }
681
682 free(name);
683 free(val);
684
685 return true;
686
687 #undef PARSE_LINE
688 }
689
read_settings_from_dir(struct settings * settings,int dirfd)690 bool read_settings_from_dir(struct settings *settings, int dirfd)
691 {
692 int fd;
693 FILE *f;
694
695 free_settings(settings);
696
697 if ((fd = openat(dirfd, settings_filename, O_RDONLY)) < 0)
698 return false;
699
700 f = fdopen(fd, "r");
701 if (!f) {
702 close(fd);
703 return false;
704 }
705
706 if (!read_settings_from_file(settings, f)) {
707 fclose(f);
708 return false;
709 }
710
711 fclose(f);
712
713 return true;
714 }
715