1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * Copyright (c) 2024 Cyril Hrubis <chrubis@suse.cz>
4 */
5 #include <sys/mount.h>
6
7 #define TST_NO_DEFAULT_MAIN
8 #include "tst_test.h"
9 #include "tst_safe_stdio.h"
10 #include "ujson.h"
11
12 static char *shell_filename;
13
run_shell(void)14 static void run_shell(void)
15 {
16 tst_run_script(shell_filename, NULL);
17 }
18
run_shell_tcnt(unsigned int n)19 static void run_shell_tcnt(unsigned int n)
20 {
21 char buf[128];
22 char *const params[] = {buf, NULL};
23
24 snprintf(buf, sizeof(buf), "%u", n);
25
26 tst_run_script(shell_filename, params);
27 }
28
29 static struct tst_test test = {
30 .runs_script = 1,
31 };
32
print_help(void)33 static void print_help(void)
34 {
35 printf("Usage: tst_shell_loader ltp_shell_test.sh ...\n");
36 }
37
38 static char *metadata;
39 static size_t metadata_size;
40 static size_t metadata_used;
41
metadata_append(const char * line)42 static void metadata_append(const char *line)
43 {
44 size_t linelen = strlen(line);
45
46 if (metadata_size - metadata_used < linelen + 1) {
47 metadata_size += 4096;
48 metadata = SAFE_REALLOC(metadata, metadata_size);
49 }
50
51 strcpy(metadata + metadata_used, line);
52 metadata_used += linelen;
53 }
54
55 enum test_attr_ids {
56 ALL_FILESYSTEMS,
57 DEV_MIN_SIZE,
58 FILESYSTEMS,
59 FORMAT_DEVICE,
60 MIN_CPUS,
61 MIN_MEM_AVAIL,
62 MIN_KVER,
63 MIN_SWAP_AVAIL,
64 MNTPOINT,
65 MOUNT_DEVICE,
66 NEEDS_ABI_BITS,
67 NEEDS_CMDS,
68 NEEDS_DEVFS,
69 NEEDS_DEVICE,
70 NEEDS_DRIVERS,
71 NEEDS_HUGETLBFS,
72 NEEDS_KCONFIGS,
73 NEEDS_ROFS,
74 NEEDS_ROOT,
75 NEEDS_TMPDIR,
76 RESTORE_WALLCLOCK,
77 SAVE_RESTORE,
78 SKIP_FILESYSTEMS,
79 SKIP_IN_COMPAT,
80 SKIP_IN_LOCKDOWN,
81 SKIP_IN_SECUREBOOT,
82 SUPPORTED_ARCHS,
83 TAGS,
84 TAINT_CHECK,
85 TCNT,
86 };
87
88 static ujson_obj_attr test_attrs[] = {
89 UJSON_OBJ_ATTR_IDX(ALL_FILESYSTEMS, "all_filesystems", UJSON_BOOL),
90 UJSON_OBJ_ATTR_IDX(DEV_MIN_SIZE, "dev_min_size", UJSON_INT),
91 UJSON_OBJ_ATTR_IDX(FILESYSTEMS, "filesystems", UJSON_ARR),
92 UJSON_OBJ_ATTR_IDX(FORMAT_DEVICE, "format_device", UJSON_BOOL),
93 UJSON_OBJ_ATTR_IDX(MIN_CPUS, "min_cpus", UJSON_INT),
94 UJSON_OBJ_ATTR_IDX(MIN_MEM_AVAIL, "min_mem_avail", UJSON_INT),
95 UJSON_OBJ_ATTR_IDX(MIN_KVER, "min_kver", UJSON_STR),
96 UJSON_OBJ_ATTR_IDX(MIN_SWAP_AVAIL, "min_swap_avail", UJSON_INT),
97 UJSON_OBJ_ATTR_IDX(MNTPOINT, "mntpoint", UJSON_STR),
98 UJSON_OBJ_ATTR_IDX(MOUNT_DEVICE, "mount_device", UJSON_BOOL),
99 UJSON_OBJ_ATTR_IDX(NEEDS_ABI_BITS, "needs_abi_bits", UJSON_INT),
100 UJSON_OBJ_ATTR_IDX(NEEDS_CMDS, "needs_cmds", UJSON_ARR),
101 UJSON_OBJ_ATTR_IDX(NEEDS_DEVFS, "needs_devfs", UJSON_BOOL),
102 UJSON_OBJ_ATTR_IDX(NEEDS_DEVICE, "needs_device", UJSON_BOOL),
103 UJSON_OBJ_ATTR_IDX(NEEDS_DRIVERS, "needs_drivers", UJSON_ARR),
104 UJSON_OBJ_ATTR_IDX(NEEDS_HUGETLBFS, "needs_hugetlbfs", UJSON_BOOL),
105 UJSON_OBJ_ATTR_IDX(NEEDS_KCONFIGS, "needs_kconfigs", UJSON_ARR),
106 UJSON_OBJ_ATTR_IDX(NEEDS_ROFS, "needs_rofs", UJSON_BOOL),
107 UJSON_OBJ_ATTR_IDX(NEEDS_ROOT, "needs_root", UJSON_BOOL),
108 UJSON_OBJ_ATTR_IDX(NEEDS_TMPDIR, "needs_tmpdir", UJSON_BOOL),
109 UJSON_OBJ_ATTR_IDX(RESTORE_WALLCLOCK, "restore_wallclock", UJSON_BOOL),
110 UJSON_OBJ_ATTR_IDX(SAVE_RESTORE, "save_restore", UJSON_ARR),
111 UJSON_OBJ_ATTR_IDX(SKIP_FILESYSTEMS, "skip_filesystems", UJSON_ARR),
112 UJSON_OBJ_ATTR_IDX(SKIP_IN_COMPAT, "skip_in_compat", UJSON_BOOL),
113 UJSON_OBJ_ATTR_IDX(SKIP_IN_LOCKDOWN, "skip_in_lockdown", UJSON_BOOL),
114 UJSON_OBJ_ATTR_IDX(SKIP_IN_SECUREBOOT, "skip_in_secureboot", UJSON_BOOL),
115 UJSON_OBJ_ATTR_IDX(SUPPORTED_ARCHS, "supported_archs", UJSON_ARR),
116 UJSON_OBJ_ATTR_IDX(TAGS, "tags", UJSON_ARR),
117 UJSON_OBJ_ATTR_IDX(TAINT_CHECK, "taint_check", UJSON_BOOL),
118 UJSON_OBJ_ATTR_IDX(TCNT, "tcnt", UJSON_INT)
119 };
120
121 static ujson_obj test_obj = {
122 .attrs = test_attrs,
123 .attr_cnt = UJSON_ARRAY_SIZE(test_attrs),
124 };
125
parse_strarr(ujson_reader * reader,ujson_val * val)126 static const char *const *parse_strarr(ujson_reader *reader, ujson_val *val)
127 {
128 unsigned int cnt = 0, i = 0;
129 char **ret;
130
131 ujson_reader_state state = ujson_reader_state_save(reader);
132
133 UJSON_ARR_FOREACH(reader, val) {
134 if (val->type != UJSON_STR) {
135 ujson_err(reader, "Expected string!");
136 return NULL;
137 }
138
139 cnt++;
140 }
141
142 ujson_reader_state_load(reader, state);
143
144 ret = SAFE_MALLOC(sizeof(char *) * (cnt + 1));
145
146 UJSON_ARR_FOREACH(reader, val) {
147 ret[i++] = strdup(val->val_str);
148 }
149
150 ret[i] = NULL;
151
152 return (const char *const *)ret;
153 }
154
155 enum fs_ids {
156 FS_MIN_KVER,
157 MKFS_OPTS,
158 MKFS_SIZE_OPT,
159 MKFS_VER,
160 MNT_FLAGS,
161 TYPE,
162 };
163
164 static ujson_obj_attr fs_attrs[] = {
165 UJSON_OBJ_ATTR_IDX(FS_MIN_KVER, "min_kver", UJSON_STR),
166 UJSON_OBJ_ATTR_IDX(MKFS_OPTS, "mkfs_opts", UJSON_ARR),
167 UJSON_OBJ_ATTR_IDX(MKFS_SIZE_OPT, "mkfs_size_opt", UJSON_STR),
168 UJSON_OBJ_ATTR_IDX(MKFS_VER, "mkfs_ver", UJSON_STR),
169 UJSON_OBJ_ATTR_IDX(MNT_FLAGS, "mnt_flags", UJSON_ARR),
170 UJSON_OBJ_ATTR_IDX(TYPE, "type", UJSON_STR),
171 };
172
173 static ujson_obj fs_obj = {
174 .attrs = fs_attrs,
175 .attr_cnt = UJSON_ARRAY_SIZE(fs_attrs),
176 };
177
parse_mnt_flags(ujson_reader * reader,ujson_val * val)178 static int parse_mnt_flags(ujson_reader *reader, ujson_val *val)
179 {
180 int ret = 0;
181
182 UJSON_ARR_FOREACH(reader, val) {
183 if (val->type != UJSON_STR) {
184 ujson_err(reader, "Expected string!");
185 return ret;
186 }
187
188 if (!strcmp(val->val_str, "RDONLY"))
189 ret |= MS_RDONLY;
190 else if (!strcmp(val->val_str, "NOATIME"))
191 ret |= MS_NOATIME;
192 else if (!strcmp(val->val_str, "NOEXEC"))
193 ret |= MS_NOEXEC;
194 else if (!strcmp(val->val_str, "NOSUID"))
195 ret |= MS_NOSUID;
196 else
197 ujson_err(reader, "Invalid mount flag");
198 }
199
200 return ret;
201 }
202
parse_filesystems(ujson_reader * reader,ujson_val * val)203 static struct tst_fs *parse_filesystems(ujson_reader *reader, ujson_val *val)
204 {
205 unsigned int i = 0, cnt = 0;
206 struct tst_fs *ret;
207
208 ujson_reader_state state = ujson_reader_state_save(reader);
209
210 UJSON_ARR_FOREACH(reader, val) {
211 if (val->type != UJSON_OBJ) {
212 ujson_err(reader, "Expected object!");
213 return NULL;
214 }
215 ujson_obj_skip(reader);
216 cnt++;
217 }
218
219 ujson_reader_state_load(reader, state);
220
221 ret = SAFE_MALLOC(sizeof(struct tst_fs) * (cnt + 1));
222 memset(ret, 0, sizeof(*ret) * (cnt+1));
223
224 UJSON_ARR_FOREACH(reader, val) {
225 UJSON_OBJ_FOREACH_FILTER(reader, val, &fs_obj, ujson_empty_obj) {
226 switch ((enum fs_ids)val->idx) {
227 case MKFS_OPTS:
228 ret[i].mkfs_opts = parse_strarr(reader, val);
229 break;
230 case MKFS_SIZE_OPT:
231 ret[i].mkfs_size_opt = strdup(val->val_str);
232 break;
233 case MKFS_VER:
234 ret[i].mkfs_ver = strdup(val->val_str);
235 break;
236 case MNT_FLAGS:
237 ret[i].mnt_flags = parse_mnt_flags(reader, val);
238 break;
239 case TYPE:
240 ret[i].type = strdup(val->val_str);
241 break;
242 case FS_MIN_KVER:
243 ret[i].min_kver = strdup(val->val_str);
244 break;
245 }
246
247 }
248
249 i++;
250 }
251
252 return ret;
253 }
254
parse_tags(ujson_reader * reader,ujson_val * val)255 static struct tst_tag *parse_tags(ujson_reader *reader, ujson_val *val)
256 {
257 unsigned int i = 0, cnt = 0;
258 struct tst_tag *ret;
259
260 ujson_reader_state state = ujson_reader_state_save(reader);
261
262 UJSON_ARR_FOREACH(reader, val) {
263 if (val->type != UJSON_ARR) {
264 ujson_err(reader, "Expected array!");
265 return NULL;
266 }
267 ujson_arr_skip(reader);
268 cnt++;
269 }
270
271 ujson_reader_state_load(reader, state);
272
273 ret = SAFE_MALLOC(sizeof(struct tst_tag) * (cnt + 1));
274 memset(&ret[cnt], 0, sizeof(ret[cnt]));
275
276 UJSON_ARR_FOREACH(reader, val) {
277 char *name = NULL;
278 char *value = NULL;
279
280 UJSON_ARR_FOREACH(reader, val) {
281 if (val->type != UJSON_STR) {
282 ujson_err(reader, "Expected string!");
283 return NULL;
284 }
285
286 if (!name) {
287 name = strdup(val->val_str);
288 } else if (!value) {
289 value = strdup(val->val_str);
290 } else {
291 ujson_err(reader, "Expected only two members!");
292 return NULL;
293 }
294 }
295
296 ret[i].name = name;
297 ret[i].value = value;
298 i++;
299 }
300
301 return ret;
302 }
303
parse_save_restore(ujson_reader * reader,ujson_val * val)304 static struct tst_path_val *parse_save_restore(ujson_reader *reader, ujson_val *val)
305 {
306 unsigned int i = 0, cnt = 0;
307 struct tst_path_val *ret;
308
309 ujson_reader_state state = ujson_reader_state_save(reader);
310
311 UJSON_ARR_FOREACH(reader, val) {
312 if (val->type != UJSON_ARR) {
313 ujson_err(reader, "Expected array!");
314 return NULL;
315 }
316 ujson_arr_skip(reader);
317 cnt++;
318 }
319
320 ujson_reader_state_load(reader, state);
321
322 ret = SAFE_MALLOC(sizeof(struct tst_path_val) * (cnt + 1));
323 memset(&ret[cnt], 0, sizeof(ret[cnt]));
324
325 UJSON_ARR_FOREACH(reader, val) {
326 char *path = NULL;
327 char *pval = NULL;
328 int flags_set = 0;
329 int val_set = 0;
330 unsigned int flags = 0;
331
332 UJSON_ARR_FOREACH(reader, val) {
333 if (!path) {
334 if (val->type != UJSON_STR) {
335 ujson_err(reader, "Expected string!");
336 return NULL;
337 }
338
339 path = strdup(val->val_str);
340 } else if (!val_set) {
341 if (val->type == UJSON_STR) {
342 pval = strdup(val->val_str);
343 } else if (val->type != UJSON_NULL) {
344 ujson_err(reader, "Expected string or NULL!");
345 return NULL;
346 }
347 val_set = 1;
348 } else if (!flags_set) {
349 if (val->type != UJSON_STR) {
350 ujson_err(reader, "Expected string!");
351 return NULL;
352 }
353
354 if (!strcmp(val->val_str, "TCONF")) {
355 flags = TST_SR_TCONF;
356 } else if (!strcmp(val->val_str, "TBROK")) {
357 flags = TST_SR_TBROK;
358 } else if (!strcmp(val->val_str, "SKIP")) {
359 flags = TST_SR_SKIP;
360 } else {
361 ujson_err(reader, "Invalid flags!");
362 return NULL;
363 }
364
365 flags_set = 1;
366 } else {
367 ujson_err(reader, "Expected only two members!");
368 return NULL;
369 }
370 }
371
372 if (!path || !flags_set) {
373 ujson_err(reader, "Expected [\"/{proc,sys}/path\", {\"TCONF\", \"TBROK\", \"TSKIP\"}]!");
374 return NULL;
375 }
376
377 ret[i].path = path;
378 ret[i].val = pval;
379 ret[i].flags = flags;
380 i++;
381 }
382
383 return ret;
384 }
385
parse_metadata(void)386 static void parse_metadata(void)
387 {
388 ujson_reader reader = UJSON_READER_INIT(metadata, metadata_used, UJSON_READER_STRICT);
389 char str_buf[128];
390 ujson_val val = UJSON_VAL_INIT(str_buf, sizeof(str_buf));
391
392 UJSON_OBJ_FOREACH_FILTER(&reader, &val, &test_obj, ujson_empty_obj) {
393 switch ((enum test_attr_ids)val.idx) {
394 case ALL_FILESYSTEMS:
395 test.all_filesystems = val.val_bool;
396 break;
397 case DEV_MIN_SIZE:
398 if (val.val_int <= 0)
399 ujson_err(&reader, "Device size must be > 0");
400 else
401 test.dev_min_size = val.val_int;
402 break;
403 case FILESYSTEMS:
404 test.filesystems = parse_filesystems(&reader, &val);
405 break;
406 case FORMAT_DEVICE:
407 test.format_device = val.val_bool;
408 break;
409 case MIN_CPUS:
410 if (val.val_int <= 0)
411 ujson_err(&reader, "Minimal number of cpus must be > 0");
412 else
413 test.min_cpus = val.val_int;
414 break;
415 case MIN_MEM_AVAIL:
416 if (val.val_int <= 0)
417 ujson_err(&reader, "Minimal available memory size must be > 0");
418 else
419 test.min_mem_avail = val.val_int;
420 break;
421 case MIN_KVER:
422 test.min_kver = strdup(val.val_str);
423 break;
424 case MIN_SWAP_AVAIL:
425 if (val.val_int <= 0)
426 ujson_err(&reader, "Minimal available swap size must be > 0");
427 else
428 test.min_swap_avail = val.val_int;
429 break;
430 case MNTPOINT:
431 test.mntpoint = strdup(val.val_str);
432 break;
433 case MOUNT_DEVICE:
434 test.mount_device = val.val_bool;
435 break;
436 case NEEDS_ABI_BITS:
437 if (val.val_int == 32 || val.val_int == 64)
438 test.needs_abi_bits = val.val_int;
439 else
440 ujson_err(&reader, "ABI bits must be 32 or 64");
441 break;
442 case NEEDS_CMDS:
443 test.needs_cmds = parse_strarr(&reader, &val);
444 break;
445 case NEEDS_DEVFS:
446 test.needs_devfs = val.val_bool;
447 break;
448 case NEEDS_DEVICE:
449 test.needs_device = val.val_bool;
450 break;
451 case NEEDS_DRIVERS:
452 test.needs_drivers = parse_strarr(&reader, &val);
453 break;
454 case NEEDS_HUGETLBFS:
455 test.needs_hugetlbfs = val.val_bool;
456 break;
457 case NEEDS_KCONFIGS:
458 test.needs_kconfigs = parse_strarr(&reader, &val);
459 break;
460 case NEEDS_ROFS:
461 test.needs_rofs = val.val_bool;
462 break;
463 case NEEDS_ROOT:
464 test.needs_root = val.val_bool;
465 break;
466 case NEEDS_TMPDIR:
467 test.needs_tmpdir = val.val_bool;
468 break;
469 case RESTORE_WALLCLOCK:
470 test.restore_wallclock = val.val_bool;
471 break;
472 case SAVE_RESTORE:
473 test.save_restore = parse_save_restore(&reader, &val);
474 break;
475 case SKIP_FILESYSTEMS:
476 test.skip_filesystems = parse_strarr(&reader, &val);
477 break;
478 case SKIP_IN_COMPAT:
479 test.skip_in_compat = val.val_bool;
480 break;
481 case SKIP_IN_LOCKDOWN:
482 test.skip_in_lockdown = val.val_bool;
483 break;
484 case SKIP_IN_SECUREBOOT:
485 test.skip_in_secureboot = val.val_bool;
486 break;
487 case SUPPORTED_ARCHS:
488 test.supported_archs = parse_strarr(&reader, &val);
489 break;
490 case TAGS:
491 test.tags = parse_tags(&reader, &val);
492 break;
493 case TAINT_CHECK:
494 test.taint_check = val.val_bool;
495 break;
496 case TCNT:
497 if (val.val_int <= 0)
498 ujson_err(&reader, "Number of tests must be > 0");
499 else
500 test.tcnt = val.val_int;
501 break;
502 }
503 }
504
505 ujson_reader_finish(&reader);
506
507 if (ujson_reader_err(&reader))
508 tst_brk(TBROK, "Invalid metadata");
509 }
510
511 enum parser_state {
512 PAR_NONE,
513 PAR_ESC,
514 PAR_DOC,
515 PAR_ENV,
516 };
517
extract_metadata(void)518 static void extract_metadata(void)
519 {
520 FILE *f;
521 char line[4096];
522 char path[4096];
523 enum parser_state state = PAR_NONE;
524 unsigned int lineno = 1;
525
526 if (tst_get_path(shell_filename, path, sizeof(path)) == -1)
527 tst_brk(TBROK, "Failed to find %s in $PATH", shell_filename);
528
529 f = SAFE_FOPEN(path, "r");
530
531 while (fgets(line, sizeof(line), f)) {
532 switch (state) {
533 case PAR_NONE:
534 if (!strcmp(line, "# ---\n"))
535 state = PAR_ESC;
536 break;
537 case PAR_ESC:
538 if (!strcmp(line, "# env\n")) {
539 state = PAR_ENV;
540 } else if (!strcmp(line, "# doc\n")) {
541 state = PAR_DOC;
542 } else {
543 tst_brk(TBROK, "%s: %u: Unknown comment block %s",
544 path, lineno, line);
545 }
546 break;
547 case PAR_ENV:
548 if (line[0] != '#') {
549 tst_brk(TBROK,
550 "%s: %u: Unexpected end of comment block!",
551 path, lineno);
552 }
553
554 if (!strcmp(line, "# ---\n"))
555 state = PAR_NONE;
556 else
557 metadata_append(line + 2);
558 break;
559 case PAR_DOC:
560 if (line[0] != '#') {
561 tst_brk(TBROK,
562 "%s: %u: Unexpected end of comment block!",
563 path, lineno);
564 }
565
566 if (!strcmp(line, "# ---\n"))
567 state = PAR_NONE;
568 break;
569 }
570
571 lineno++;
572 }
573
574 fclose(f);
575 }
576
prepare_test_struct(void)577 static void prepare_test_struct(void)
578 {
579 extract_metadata();
580
581 if (metadata)
582 parse_metadata();
583 else
584 tst_brk(TBROK, "No metadata found!");
585 }
586
main(int argc,char * argv[])587 int main(int argc, char *argv[])
588 {
589 if (argc < 2)
590 goto help;
591
592 shell_filename = argv[1];
593
594 prepare_test_struct();
595
596 if (test.tcnt)
597 test.test = run_shell_tcnt;
598 else
599 test.test_all = run_shell;
600
601 tst_run_tcases(argc - 1, argv + 1, &test);
602 help:
603 print_help();
604 return 1;
605 }
606