// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (c) 2019-2021 Cyril Hrubis * Copyright (c) 2020 Petr Vorel */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include "data_storage.h" #define INCLUDE_PATH_MAX 5 static int verbose; static char *cmdline_includepath[INCLUDE_PATH_MAX]; static unsigned int cmdline_includepaths; static char *includepath; #define WARN(str) fprintf(stderr, "WARNING: " str "\n") static void oneline_comment(FILE *f) { int c; do { c = getc(f); } while (c != '\n'); } static const char *eat_asterisk_space(const char *c) { unsigned int i = 0; while (isspace(c[i])) i++; if (c[i] == '*') { if (isspace(c[i+1])) i++; return &c[i+1]; } return c; } static void multiline_comment(FILE *f, struct data_node *doc) { int c; int state = 0; char buf[4096]; unsigned int bufp = 0; for (;;) { c = getc(f); if (doc) { if (c == '\n') { struct data_node *line; buf[bufp] = 0; line = data_node_string(eat_asterisk_space(buf)); if (data_node_array_add(doc, line)) WARN("doc string comment truncated"); bufp = 0; continue; } if (bufp + 1 >= sizeof(buf)) continue; buf[bufp++] = c; } switch (state) { case 0: if (c == '*') state = 1; break; case 1: switch (c) { case '/': return; case '*': continue; default: state = 0; break; } break; } } } static const char doc_prefix[] = "\\\n"; static void maybe_doc_comment(FILE *f, struct data_node *doc) { int c, i; for (i = 0; doc_prefix[i]; i++) { c = getc(f); if (c == doc_prefix[i]) continue; if (c == '*') ungetc(c, f); multiline_comment(f, NULL); return; } multiline_comment(f, doc); } static void maybe_comment(FILE *f, struct data_node *doc) { int c = getc(f); switch (c) { case '/': oneline_comment(f); break; case '*': maybe_doc_comment(f, doc); break; default: ungetc(c, f); break; } } static char *next_token2(FILE *f, char *buf, size_t buf_len, struct data_node *doc) { size_t i = 0; int c; int in_str = 0; buf_len--; for (;;) { c = fgetc(f); if (c == EOF) goto exit; if (in_str) { if (c == '"') { if (i == 0 || buf[i-1] != '\\') goto exit; } if (i < buf_len) buf[i++] = c; continue; } switch (c) { case '{': case '}': case ';': case '(': case ')': case '=': case ',': case '[': case ']': case '#': if (i) { ungetc(c, f); goto exit; } if (i < buf_len) buf[i++] = c; goto exit; case '0' ... '9': case 'a' ... 'z': case 'A' ... 'Z': case '.': case '_': case '-': buf[i++] = c; break; case '/': maybe_comment(f, doc); break; case '"': in_str = 1; break; case ' ': case '\n': case '\t': if (i) goto exit; break; } } exit: if (i == 0 && !in_str) return NULL; buf[i] = 0; return buf; } static char *next_token(FILE *f, struct data_node *doc) { static char buf[4096]; return next_token2(f, buf, sizeof(buf), doc); } static FILE *open_file(const char *dir, const char *fname) { FILE *f; char *path; if (asprintf(&path, "%s/%s", dir, fname) < 0) return NULL; f = fopen(path, "r"); free(path); return f; } static FILE *open_include(FILE *f) { char buf[256], *fname; FILE *inc; unsigned int i; if (!fscanf(f, "%s\n", buf)) return NULL; if (buf[0] != '"') return NULL; fname = buf + 1; if (!buf[0]) return NULL; fname[strlen(fname)-1] = 0; inc = open_file(includepath, fname); if (inc) { if (verbose) fprintf(stderr, "INCLUDE %s/%s\n", includepath, fname); return inc; } for (i = 0; i < cmdline_includepaths; i++) { inc = open_file(cmdline_includepath[i], fname); if (!inc) continue; if (verbose) { fprintf(stderr, "INCLUDE %s/%s\n", cmdline_includepath[i], fname); } return inc; } return NULL; } static void close_include(FILE *inc) { if (verbose) fprintf(stderr, "INCLUDE END\n"); fclose(inc); } static int parse_array(FILE *f, struct data_node *node) { const char *token; for (;;) { if (!(token = next_token(f, NULL))) return 1; if (!strcmp(token, "{")) { struct data_node *ret = data_node_array(); parse_array(f, ret); if (data_node_array_len(ret)) data_node_array_add(node, ret); else data_node_free(ret); continue; } if (!strcmp(token, "}")) return 0; if (!strcmp(token, ",")) continue; if (!strcmp(token, "NULL")) continue; struct data_node *str = data_node_string(token); data_node_array_add(node, str); } return 0; } static void try_apply_macro(char **res) { ENTRY macro = { .key = *res, }; ENTRY *ret; ret = hsearch(macro, FIND); if (!ret) return; if (verbose) fprintf(stderr, "APPLYING MACRO %s=%s\n", ret->key, (char*)ret->data); *res = ret->data; } static int parse_get_array_len(FILE *f) { const char *token; int cnt = 0, depth = 0, prev_comma = 0; if (!(token = next_token(f, NULL))) return 0; if (strcmp(token, "{")) return 0; for (;;) { if (!(token = next_token(f, NULL))) return 0; if (!strcmp(token, "{")) depth++; if (!strcmp(token, "}")) depth--; else prev_comma = 0; if (!strcmp(token, ",") && !depth) { prev_comma = 1; cnt++; } if (depth < 0) return cnt + !prev_comma; } } static void look_for_array_size(FILE *f, const char *arr_id, struct data_node **res) { const char *token; char buf[2][2048] = {}; int cur_buf = 0; int prev_buf = 1; for (;;) { if (!(token = next_token2(f, buf[cur_buf], sizeof(buf[cur_buf]), NULL))) break; if (!strcmp(token, "=") && !strcmp(buf[prev_buf], arr_id)) { int arr_len = parse_get_array_len(f); if (verbose) fprintf(stderr, "ARRAY %s LENGTH = %i\n", arr_id, arr_len); *res = data_node_int(arr_len); break; } if (strcmp(buf[cur_buf], "]") && strcmp(buf[cur_buf], "[")) { cur_buf = !cur_buf; prev_buf = !prev_buf; } } } static int parse_array_size(FILE *f, struct data_node **res) { const char *token; char *arr_id; long pos; int hash = 0; *res = NULL; if (!(token = next_token(f, NULL))) return 1; if (strcmp(token, "(")) return 1; if (!(token = next_token(f, NULL))) return 1; arr_id = strdup(token); if (verbose) fprintf(stderr, "COMPUTING ARRAY '%s' LENGHT\n", arr_id); pos = ftell(f); rewind(f); look_for_array_size(f, arr_id, res); if (!*res) { FILE *inc; rewind(f); for (;;) { if (!(token = next_token(f, NULL))) break; if (token[0] == '#') { hash = 1; continue; } if (!hash) continue; if (!strcmp(token, "include")) { inc = open_include(f); if (inc) { look_for_array_size(inc, arr_id, res); close_include(inc); } } if (*res) break; } } free(arr_id); if (fseek(f, pos, SEEK_SET)) return 1; return 0; } static int parse_test_struct(FILE *f, struct data_node *doc, struct data_node *node) { char *token; char *id = NULL; int state = 0; struct data_node *ret; for (;;) { if (!(token = next_token(f, doc))) return 1; if (!strcmp(token, "}")) return 0; switch (state) { case 0: id = strdup(token); state = 1; continue; case 1: if (!strcmp(token, "=")) state = 2; else WARN("Expected '='"); continue; case 2: if (!strcmp(token, "(")) { state = 3; continue; } break; case 3: if (!strcmp(token, ")")) state = 2; continue; case 4: if (!strcmp(token, ",")) state = 0; continue; } if (!strcmp(token, "{")) { ret = data_node_array(); parse_array(f, ret); } else if (!strcmp(token, "ARRAY_SIZE")) { if (parse_array_size(f, &ret)) return 1; } else { try_apply_macro(&token); ret = data_node_string(token); } if (!ret) continue; const char *key = id; if (key[0] == '.') key++; data_node_hash_add(node, key, ret); free(id); state = 4; } } static const char *tokens[] = { "static", "struct", "tst_test", "test", "=", "{", }; static void macro_get_string(FILE *f, char *buf, char *buf_end) { int c; char *buf_start = buf; for (;;) { c = fgetc(f); switch (c) { case EOF: *buf = 0; return; case '"': if (buf == buf_start || buf[-1] != '\\') { *buf = 0; return; } buf[-1] = '"'; break; default: if (buf < buf_end) *(buf++) = c; } } } static void macro_get_val(FILE *f, char *buf, size_t buf_len) { int c, prev = 0; char *buf_end = buf + buf_len - 1; while (isspace(c = fgetc(f))); if (c == '"') { macro_get_string(f, buf, buf_end); return; } for (;;) { switch (c) { case '\n': if (prev == '\\') { buf--; } else { *buf = 0; return; } break; case EOF: *buf = 0; return; case ' ': case '\t': break; default: if (buf < buf_end) *(buf++) = c; } prev = c; c = fgetc(f); } } static void parse_macro(FILE *f) { char name[128]; char val[256]; if (!fscanf(f, "%s[^\n]", name)) return; if (fgetc(f) == '\n') return; macro_get_val(f, val, sizeof(val)); if (name[0] == '_') return; ENTRY e = { .key = strdup(name), .data = strdup(val), }; if (verbose) fprintf(stderr, " MACRO %s=%s\n", e.key, (char*)e.data); hsearch(e, ENTER); } static void parse_include_macros(FILE *f) { FILE *inc; const char *token; int hash = 0; inc = open_include(f); if (!inc) return; while ((token = next_token(inc, NULL))) { if (token[0] == '#') { hash = 1; continue; } if (!hash) continue; if (!strcmp(token, "define")) parse_macro(inc); hash = 0; } close_include(inc); } static struct data_node *parse_file(const char *fname) { int state = 0, found = 0; const char *token; if (access(fname, F_OK)) { fprintf(stderr, "file %s does not exist\n", fname); return NULL; } FILE *f = fopen(fname, "r"); includepath = dirname(strdup(fname)); struct data_node *res = data_node_hash(); struct data_node *doc = data_node_array(); while ((token = next_token(f, doc))) { if (state < 6 && !strcmp(tokens[state], token)) { state++; } else { if (token[0] == '#') { token = next_token(f, doc); if (token) { if (!strcmp(token, "define")) parse_macro(f); if (!strcmp(token, "include")) parse_include_macros(f); } } state = 0; } if (state < 6) continue; found = 1; parse_test_struct(f, doc, res); } if (data_node_array_len(doc)) { data_node_hash_add(res, "doc", doc); found = 1; } else { data_node_free(doc); } fclose(f); if (!found) { data_node_free(res); return NULL; } return res; } static struct typemap { const char *id; enum data_type type; } tst_test_typemap[] = { {.id = "test_variants", .type = DATA_INT}, {} }; static void convert_str2int(struct data_node *res, const char *id, const char *str_val) { long val; char *endptr; errno = 0; val = strtol(str_val, &endptr, 10); if (errno || *endptr) { fprintf(stderr, "Cannot convert %s value %s to int!\n", id, str_val); exit(1); } if (verbose) fprintf(stderr, "NORMALIZING %s TO INT %li\n", id, val); data_node_hash_del(res, id); data_node_hash_add(res, id, data_node_int(val)); } static void check_normalize_types(struct data_node *res) { unsigned int i; for (i = 0; tst_test_typemap[i].id; i++) { struct data_node *n; struct typemap *typemap = &tst_test_typemap[i]; n = data_node_hash_get(res, typemap->id); if (!n) continue; if (n->type == typemap->type) continue; if (n->type == DATA_STRING && typemap->type == DATA_INT) { convert_str2int(res, typemap->id, n->string.val); continue; } fprintf(stderr, "Cannot convert %s from %s to %s!\n", typemap->id, data_type_name(n->type), data_type_name(typemap->type)); exit(1); } } static const char *filter_out[] = { "bufs", "cleanup", "mntpoint", "setup", "tcnt", "test", "test_all", NULL }; static struct implies { const char *flag; const char **implies; } implies[] = { {"mount_device", (const char *[]) {"format_device", "needs_device", "needs_tmpdir", NULL}}, {"format_device", (const char *[]) {"needs_device", "needs_tmpdir", NULL}}, {"all_filesystems", (const char *[]) {"needs_device", "needs_tmpdir", NULL}}, {"needs_device", (const char *[]) {"needs_tmpdir", NULL}}, {"needs_checkpoints", (const char *[]) {"needs_tmpdir", NULL}}, {"resource_files", (const char *[]) {"needs_tmpdir", NULL}}, {NULL, (const char *[]) {NULL}} }; const char *strip_name(char *path) { char *name = basename(path); size_t len = strlen(name); if (len > 2 && name[len-1] == 'c' && name[len-2] == '.') name[len-2] = '\0'; return name; } static void print_help(const char *prgname) { printf("usage: %s [-vh] input.c\n\n", prgname); printf("-v sets verbose mode\n"); printf("-I add include path\n"); printf("-h prints this help\n\n"); exit(0); } int main(int argc, char *argv[]) { unsigned int i, j; struct data_node *res; int opt; while ((opt = getopt(argc, argv, "hI:v")) != -1) { switch (opt) { case 'h': print_help(argv[0]); break; case 'I': if (cmdline_includepaths >= INCLUDE_PATH_MAX) { fprintf(stderr, "Too much include paths!"); exit(1); } cmdline_includepath[cmdline_includepaths++] = optarg; break; case 'v': verbose = 1; break; } } if (optind >= argc) { fprintf(stderr, "No input filename.c\n"); return 1; } if (!hcreate(128)) { fprintf(stderr, "Failed to initialize hash table\n"); return 1; } res = parse_file(argv[optind]); if (!res) return 0; /* Filter out useless data */ for (i = 0; filter_out[i]; i++) data_node_hash_del(res, filter_out[i]); /* Normalize the result */ for (i = 0; implies[i].flag; i++) { if (data_node_hash_get(res, implies[i].flag)) { for (j = 0; implies[i].implies[j]; j++) { if (data_node_hash_get(res, implies[i].implies[j])) fprintf(stderr, "%s: useless tag: %s\n", argv[1], implies[i].implies[j]); } } } /* Normalize types */ check_normalize_types(res); for (i = 0; implies[i].flag; i++) { if (data_node_hash_get(res, implies[i].flag)) { for (j = 0; implies[i].implies[j]; j++) { if (!data_node_hash_get(res, implies[i].implies[j])) data_node_hash_add(res, implies[i].implies[j], data_node_string("1")); } } } data_node_hash_add(res, "fname", data_node_string(argv[optind])); printf(" \"%s\": ", strip_name(argv[optind])); data_to_json(res, stdout, 2); data_node_free(res); return 0; }