unplugged-system/external/ltp/metadata/metaparse.c

904 lines
14 KiB
C

// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (c) 2019-2021 Cyril Hrubis <chrubis@suse.cz>
* Copyright (c) 2020 Petr Vorel <pvorel@suse.cz>
*/
#define _GNU_SOURCE
#include <search.h>
#include <stdio.h>
#include <string.h>
#include <libgen.h>
#include <ctype.h>
#include <unistd.h>
#include <errno.h>
#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[optind], 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;
}