* Copyright (c) 2024 Cyril Hrubis <chrubis@suse.cz>
*/
#include <sys/mount.h>
#define TST_NO_DEFAULT_MAIN
#include "tst_test.h"
#include "tst_safe_stdio.h"
#include "ujson.h"
static char *shell_filename;
static void run_shell(void)
{
tst_run_script(shell_filename, NULL);
}
static void run_shell_tcnt(unsigned int n)
{
char buf[128];
char *const params[] = {buf, NULL};
snprintf(buf, sizeof(buf), "%u", n);
tst_run_script(shell_filename, params);
}
static struct tst_test test = {
.runs_script = 1,
};
static void print_help(void)
{
printf("Usage: tst_shell_loader ltp_shell_test.sh ...\n");
}
static char *metadata;
static size_t metadata_size;
static size_t metadata_used;
static void metadata_append(const char *line)
{
size_t linelen = strlen(line);
if (metadata_size - metadata_used < linelen + 1) {
metadata_size += 4096;
metadata = SAFE_REALLOC(metadata, metadata_size);
}
strcpy(metadata + metadata_used, line);
metadata_used += linelen;
}
enum test_attr_ids {
ALL_FILESYSTEMS,
DEV_MIN_SIZE,
FILESYSTEMS,
FORMAT_DEVICE,
MIN_CPUS,
MIN_MEM_AVAIL,
MIN_KVER,
MIN_SWAP_AVAIL,
MNTPOINT,
MOUNT_DEVICE,
NEEDS_ABI_BITS,
NEEDS_CMDS,
NEEDS_DEVFS,
NEEDS_DEVICE,
NEEDS_DRIVERS,
NEEDS_HUGETLBFS,
NEEDS_KCONFIGS,
NEEDS_ROFS,
NEEDS_ROOT,
NEEDS_TMPDIR,
RESTORE_WALLCLOCK,
SAVE_RESTORE,
SKIP_FILESYSTEMS,
SKIP_IN_COMPAT,
SKIP_IN_LOCKDOWN,
SKIP_IN_SECUREBOOT,
SUPPORTED_ARCHS,
TAGS,
TAINT_CHECK,
TCNT,
};
static ujson_obj_attr test_attrs[] = {
UJSON_OBJ_ATTR_IDX(ALL_FILESYSTEMS, "all_filesystems", UJSON_BOOL),
UJSON_OBJ_ATTR_IDX(DEV_MIN_SIZE, "dev_min_size", UJSON_INT),
UJSON_OBJ_ATTR_IDX(FILESYSTEMS, "filesystems", UJSON_ARR),
UJSON_OBJ_ATTR_IDX(FORMAT_DEVICE, "format_device", UJSON_BOOL),
UJSON_OBJ_ATTR_IDX(MIN_CPUS, "min_cpus", UJSON_INT),
UJSON_OBJ_ATTR_IDX(MIN_MEM_AVAIL, "min_mem_avail", UJSON_INT),
UJSON_OBJ_ATTR_IDX(MIN_KVER, "min_kver", UJSON_STR),
UJSON_OBJ_ATTR_IDX(MIN_SWAP_AVAIL, "min_swap_avail", UJSON_INT),
UJSON_OBJ_ATTR_IDX(MNTPOINT, "mntpoint", UJSON_STR),
UJSON_OBJ_ATTR_IDX(MOUNT_DEVICE, "mount_device", UJSON_BOOL),
UJSON_OBJ_ATTR_IDX(NEEDS_ABI_BITS, "needs_abi_bits", UJSON_INT),
UJSON_OBJ_ATTR_IDX(NEEDS_CMDS, "needs_cmds", UJSON_ARR),
UJSON_OBJ_ATTR_IDX(NEEDS_DEVFS, "needs_devfs", UJSON_BOOL),
UJSON_OBJ_ATTR_IDX(NEEDS_DEVICE, "needs_device", UJSON_BOOL),
UJSON_OBJ_ATTR_IDX(NEEDS_DRIVERS, "needs_drivers", UJSON_ARR),
UJSON_OBJ_ATTR_IDX(NEEDS_HUGETLBFS, "needs_hugetlbfs", UJSON_BOOL),
UJSON_OBJ_ATTR_IDX(NEEDS_KCONFIGS, "needs_kconfigs", UJSON_ARR),
UJSON_OBJ_ATTR_IDX(NEEDS_ROFS, "needs_rofs", UJSON_BOOL),
UJSON_OBJ_ATTR_IDX(NEEDS_ROOT, "needs_root", UJSON_BOOL),
UJSON_OBJ_ATTR_IDX(NEEDS_TMPDIR, "needs_tmpdir", UJSON_BOOL),
UJSON_OBJ_ATTR_IDX(RESTORE_WALLCLOCK, "restore_wallclock", UJSON_BOOL),
UJSON_OBJ_ATTR_IDX(SAVE_RESTORE, "save_restore", UJSON_ARR),
UJSON_OBJ_ATTR_IDX(SKIP_FILESYSTEMS, "skip_filesystems", UJSON_ARR),
UJSON_OBJ_ATTR_IDX(SKIP_IN_COMPAT, "skip_in_compat", UJSON_BOOL),
UJSON_OBJ_ATTR_IDX(SKIP_IN_LOCKDOWN, "skip_in_lockdown", UJSON_BOOL),
UJSON_OBJ_ATTR_IDX(SKIP_IN_SECUREBOOT, "skip_in_secureboot", UJSON_BOOL),
UJSON_OBJ_ATTR_IDX(SUPPORTED_ARCHS, "supported_archs", UJSON_ARR),
UJSON_OBJ_ATTR_IDX(TAGS, "tags", UJSON_ARR),
UJSON_OBJ_ATTR_IDX(TAINT_CHECK, "taint_check", UJSON_BOOL),
UJSON_OBJ_ATTR_IDX(TCNT, "tcnt", UJSON_INT)
};
static ujson_obj test_obj = {
.attrs = test_attrs,
.attr_cnt = UJSON_ARRAY_SIZE(test_attrs),
};
static const char *const *parse_strarr(ujson_reader *reader, ujson_val *val)
{
unsigned int cnt = 0, i = 0;
char **ret;
ujson_reader_state state = ujson_reader_state_save(reader);
UJSON_ARR_FOREACH(reader, val) {
if (val->type != UJSON_STR) {
ujson_err(reader, "Expected string!");
return NULL;
}
cnt++;
}
ujson_reader_state_load(reader, state);
ret = SAFE_MALLOC(sizeof(char *) * (cnt + 1));
UJSON_ARR_FOREACH(reader, val) {
ret[i++] = strdup(val->val_str);
}
ret[i] = NULL;
return (const char *const *)ret;
}
enum fs_ids {
FS_MIN_KVER,
MKFS_OPTS,
MKFS_SIZE_OPT,
MKFS_VER,
MNT_FLAGS,
TYPE,
};
static ujson_obj_attr fs_attrs[] = {
UJSON_OBJ_ATTR_IDX(FS_MIN_KVER, "min_kver", UJSON_STR),
UJSON_OBJ_ATTR_IDX(MKFS_OPTS, "mkfs_opts", UJSON_ARR),
UJSON_OBJ_ATTR_IDX(MKFS_SIZE_OPT, "mkfs_size_opt", UJSON_STR),
UJSON_OBJ_ATTR_IDX(MKFS_VER, "mkfs_ver", UJSON_STR),
UJSON_OBJ_ATTR_IDX(MNT_FLAGS, "mnt_flags", UJSON_ARR),
UJSON_OBJ_ATTR_IDX(TYPE, "type", UJSON_STR),
};
static ujson_obj fs_obj = {
.attrs = fs_attrs,
.attr_cnt = UJSON_ARRAY_SIZE(fs_attrs),
};
static int parse_mnt_flags(ujson_reader *reader, ujson_val *val)
{
int ret = 0;
UJSON_ARR_FOREACH(reader, val) {
if (val->type != UJSON_STR) {
ujson_err(reader, "Expected string!");
return ret;
}
if (!strcmp(val->val_str, "RDONLY"))
ret |= MS_RDONLY;
else if (!strcmp(val->val_str, "NOATIME"))
ret |= MS_NOATIME;
else if (!strcmp(val->val_str, "NOEXEC"))
ret |= MS_NOEXEC;
else if (!strcmp(val->val_str, "NOSUID"))
ret |= MS_NOSUID;
else
ujson_err(reader, "Invalid mount flag");
}
return ret;
}
static struct tst_fs *parse_filesystems(ujson_reader *reader, ujson_val *val)
{
unsigned int i = 0, cnt = 0;
struct tst_fs *ret;
ujson_reader_state state = ujson_reader_state_save(reader);
UJSON_ARR_FOREACH(reader, val) {
if (val->type != UJSON_OBJ) {
ujson_err(reader, "Expected object!");
return NULL;
}
ujson_obj_skip(reader);
cnt++;
}
ujson_reader_state_load(reader, state);
ret = SAFE_MALLOC(sizeof(struct tst_fs) * (cnt + 1));
memset(ret, 0, sizeof(*ret) * (cnt+1));
UJSON_ARR_FOREACH(reader, val) {
UJSON_OBJ_FOREACH_FILTER(reader, val, &fs_obj, ujson_empty_obj) {
switch ((enum fs_ids)val->idx) {
case MKFS_OPTS:
ret[i].mkfs_opts = parse_strarr(reader, val);
break;
case MKFS_SIZE_OPT:
ret[i].mkfs_size_opt = strdup(val->val_str);
break;
case MKFS_VER:
ret[i].mkfs_ver = strdup(val->val_str);
break;
case MNT_FLAGS:
ret[i].mnt_flags = parse_mnt_flags(reader, val);
break;
case TYPE:
ret[i].type = strdup(val->val_str);
break;
case FS_MIN_KVER:
ret[i].min_kver = strdup(val->val_str);
break;
}
}
i++;
}
return ret;
}
static struct tst_tag *parse_tags(ujson_reader *reader, ujson_val *val)
{
unsigned int i = 0, cnt = 0;
struct tst_tag *ret;
ujson_reader_state state = ujson_reader_state_save(reader);
UJSON_ARR_FOREACH(reader, val) {
if (val->type != UJSON_ARR) {
ujson_err(reader, "Expected array!");
return NULL;
}
ujson_arr_skip(reader);
cnt++;
}
ujson_reader_state_load(reader, state);
ret = SAFE_MALLOC(sizeof(struct tst_tag) * (cnt + 1));
memset(&ret[cnt], 0, sizeof(ret[cnt]));
UJSON_ARR_FOREACH(reader, val) {
char *name = NULL;
char *value = NULL;
UJSON_ARR_FOREACH(reader, val) {
if (val->type != UJSON_STR) {
ujson_err(reader, "Expected string!");
return NULL;
}
if (!name) {
name = strdup(val->val_str);
} else if (!value) {
value = strdup(val->val_str);
} else {
ujson_err(reader, "Expected only two members!");
return NULL;
}
}
ret[i].name = name;
ret[i].value = value;
i++;
}
return ret;
}
static struct tst_path_val *parse_save_restore(ujson_reader *reader, ujson_val *val)
{
unsigned int i = 0, cnt = 0;
struct tst_path_val *ret;
ujson_reader_state state = ujson_reader_state_save(reader);
UJSON_ARR_FOREACH(reader, val) {
if (val->type != UJSON_ARR) {
ujson_err(reader, "Expected array!");
return NULL;
}
ujson_arr_skip(reader);
cnt++;
}
ujson_reader_state_load(reader, state);
ret = SAFE_MALLOC(sizeof(struct tst_path_val) * (cnt + 1));
memset(&ret[cnt], 0, sizeof(ret[cnt]));
UJSON_ARR_FOREACH(reader, val) {
char *path = NULL;
char *pval = NULL;
int flags_set = 0;
int val_set = 0;
unsigned int flags = 0;
UJSON_ARR_FOREACH(reader, val) {
if (!path) {
if (val->type != UJSON_STR) {
ujson_err(reader, "Expected string!");
return NULL;
}
path = strdup(val->val_str);
} else if (!val_set) {
if (val->type == UJSON_STR) {
pval = strdup(val->val_str);
} else if (val->type != UJSON_NULL) {
ujson_err(reader, "Expected string or NULL!");
return NULL;
}
val_set = 1;
} else if (!flags_set) {
if (val->type != UJSON_STR) {
ujson_err(reader, "Expected string!");
return NULL;
}
if (!strcmp(val->val_str, "TCONF")) {
flags = TST_SR_TCONF;
} else if (!strcmp(val->val_str, "TBROK")) {
flags = TST_SR_TBROK;
} else if (!strcmp(val->val_str, "SKIP")) {
flags = TST_SR_SKIP;
} else {
ujson_err(reader, "Invalid flags!");
return NULL;
}
flags_set = 1;
} else {
ujson_err(reader, "Expected only two members!");
return NULL;
}
}
if (!path || !flags_set) {
ujson_err(reader, "Expected [\"/{proc,sys}/path\", {\"TCONF\", \"TBROK\", \"TSKIP\"}]!");
return NULL;
}
ret[i].path = path;
ret[i].val = pval;
ret[i].flags = flags;
i++;
}
return ret;
}
static void parse_metadata(void)
{
ujson_reader reader = UJSON_READER_INIT(metadata, metadata_used, UJSON_READER_STRICT);
char str_buf[128];
ujson_val val = UJSON_VAL_INIT(str_buf, sizeof(str_buf));
UJSON_OBJ_FOREACH_FILTER(&reader, &val, &test_obj, ujson_empty_obj) {
switch ((enum test_attr_ids)val.idx) {
case ALL_FILESYSTEMS:
test.all_filesystems = val.val_bool;
break;
case DEV_MIN_SIZE:
if (val.val_int <= 0)
ujson_err(&reader, "Device size must be > 0");
else
test.dev_min_size = val.val_int;
break;
case FILESYSTEMS:
test.filesystems = parse_filesystems(&reader, &val);
break;
case FORMAT_DEVICE:
test.format_device = val.val_bool;
break;
case MIN_CPUS:
if (val.val_int <= 0)
ujson_err(&reader, "Minimal number of cpus must be > 0");
else
test.min_cpus = val.val_int;
break;
case MIN_MEM_AVAIL:
if (val.val_int <= 0)
ujson_err(&reader, "Minimal available memory size must be > 0");
else
test.min_mem_avail = val.val_int;
break;
case MIN_KVER:
test.min_kver = strdup(val.val_str);
break;
case MIN_SWAP_AVAIL:
if (val.val_int <= 0)
ujson_err(&reader, "Minimal available swap size must be > 0");
else
test.min_swap_avail = val.val_int;
break;
case MNTPOINT:
test.mntpoint = strdup(val.val_str);
break;
case MOUNT_DEVICE:
test.mount_device = val.val_bool;
break;
case NEEDS_ABI_BITS:
if (val.val_int == 32 || val.val_int == 64)
test.needs_abi_bits = val.val_int;
else
ujson_err(&reader, "ABI bits must be 32 or 64");
break;
case NEEDS_CMDS:
test.needs_cmds = parse_strarr(&reader, &val);
break;
case NEEDS_DEVFS:
test.needs_devfs = val.val_bool;
break;
case NEEDS_DEVICE:
test.needs_device = val.val_bool;
break;
case NEEDS_DRIVERS:
test.needs_drivers = parse_strarr(&reader, &val);
break;
case NEEDS_HUGETLBFS:
test.needs_hugetlbfs = val.val_bool;
break;
case NEEDS_KCONFIGS:
test.needs_kconfigs = parse_strarr(&reader, &val);
break;
case NEEDS_ROFS:
test.needs_rofs = val.val_bool;
break;
case NEEDS_ROOT:
test.needs_root = val.val_bool;
break;
case NEEDS_TMPDIR:
test.needs_tmpdir = val.val_bool;
break;
case RESTORE_WALLCLOCK:
test.restore_wallclock = val.val_bool;
break;
case SAVE_RESTORE:
test.save_restore = parse_save_restore(&reader, &val);
break;
case SKIP_FILESYSTEMS:
test.skip_filesystems = parse_strarr(&reader, &val);
break;
case SKIP_IN_COMPAT:
test.skip_in_compat = val.val_bool;
break;
case SKIP_IN_LOCKDOWN:
test.skip_in_lockdown = val.val_bool;
break;
case SKIP_IN_SECUREBOOT:
test.skip_in_secureboot = val.val_bool;
break;
case SUPPORTED_ARCHS:
test.supported_archs = parse_strarr(&reader, &val);
break;
case TAGS:
test.tags = parse_tags(&reader, &val);
break;
case TAINT_CHECK:
test.taint_check = val.val_bool;
break;
case TCNT:
if (val.val_int <= 0)
ujson_err(&reader, "Number of tests must be > 0");
else
test.tcnt = val.val_int;
break;
}
}
ujson_reader_finish(&reader);
if (ujson_reader_err(&reader))
tst_brk(TBROK, "Invalid metadata");
}
enum parser_state {
PAR_NONE,
PAR_ESC,
PAR_DOC,
PAR_ENV,
};
static void extract_metadata(void)
{
FILE *f;
char line[4096];
char path[4096];
enum parser_state state = PAR_NONE;
unsigned int lineno = 1;
if (tst_get_path(shell_filename, path, sizeof(path)) == -1)
tst_brk(TBROK, "Failed to find %s in $PATH", shell_filename);
f = SAFE_FOPEN(path, "r");
while (fgets(line, sizeof(line), f)) {
switch (state) {
case PAR_NONE:
if (!strcmp(line, "# ---\n"))
state = PAR_ESC;
break;
case PAR_ESC:
if (!strcmp(line, "# env\n")) {
state = PAR_ENV;
} else if (!strcmp(line, "# doc\n")) {
state = PAR_DOC;
} else {
tst_brk(TBROK, "%s: %u: Unknown comment block %s",
path, lineno, line);
}
break;
case PAR_ENV:
if (line[0] != '#') {
tst_brk(TBROK,
"%s: %u: Unexpected end of comment block!",
path, lineno);
}
if (!strcmp(line, "# ---\n"))
state = PAR_NONE;
else
metadata_append(line + 2);
break;
case PAR_DOC:
if (line[0] != '#') {
tst_brk(TBROK,
"%s: %u: Unexpected end of comment block!",
path, lineno);
}
if (!strcmp(line, "# ---\n"))
state = PAR_NONE;
break;
}
lineno++;
}
fclose(f);
}
static void prepare_test_struct(void)
{
extract_metadata();
if (metadata)
parse_metadata();
else
tst_brk(TBROK, "No metadata found!");
}
int main(int argc, char *argv[])
{
if (argc < 2)
goto help;
shell_filename = argv[1];
prepare_test_struct();
if (test.tcnt)
test.test = run_shell_tcnt;
else
test.test_all = run_shell;
tst_run_tcases(argc - 1, argv + 1, &test);
help:
print_help();
return 1;
}