1 /* radare - LGPL - Copyright 2020 - thestr4ng3r */
2
3 #include "r2r.h"
4
5 #include <assert.h>
6
7 #define LINEFMT "%s, line %"PFMT64u": "
8
r2r_cmd_test_new(void)9 R_API R2RCmdTest *r2r_cmd_test_new(void) {
10 return R_NEW0 (R2RCmdTest);
11 }
12
r2r_cmd_test_free(R2RCmdTest * test)13 R_API void r2r_cmd_test_free(R2RCmdTest *test) {
14 if (!test) {
15 return;
16 }
17 #define DO_KEY_STR(key, field) free (test->field.value);
18 R2R_CMD_TEST_FOREACH_RECORD(DO_KEY_STR, R2R_CMD_TEST_FOREACH_RECORD_NOP, R2R_CMD_TEST_FOREACH_RECORD_NOP)
19 #undef DO_KEY_STR
20 free (test);
21 }
22
readline(char * buf,size_t * linesz)23 static char *readline(char *buf, size_t *linesz) {
24 char *end = strchr (buf, '\n');
25 if (end) {
26 size_t len = end - buf;
27 *end = '\0';
28 if (len > 0 && buf[len - 1] == '\r') {
29 buf[len - 1] = '\0';
30 len--;
31 }
32 *linesz = len;
33 return end + 1;
34 } else {
35 *linesz = strlen (buf);
36 return NULL;
37 }
38 }
39
40 // read the (possibly multiline) string value of some key in the file
41 // e.g. for
42 //
43 // 0 CMDS=<<EOF
44 // 1 Hello
45 // 2 World
46 // 3 EOF
47 // 4 ...
48 //
49 // if nextline is at the beginning of line 1,
50 // read_string_val(&nextline, "<<EOF\0")
51 // will return "Hello\nWorld\n" with nextline being at the beginning of line 4 afterwards.
read_string_val(char ** nextline,const char * val,ut64 * linenum)52 static char *read_string_val(char **nextline, const char *val, ut64 *linenum) {
53 if (val[0] == '\'') {
54 size_t len = strlen (val);
55 if (len > 1 && val[len - 1] == '\'') {
56 eprintf ("Error: Invalid string syntax, use <<EOF instead of '...'\n");
57 return NULL;
58 }
59 }
60 if (val[0] == '<' && val[1] == '<') {
61 // <<EOF syntax
62 const char *endtoken = val + 2;
63 if (!*endtoken) {
64 eprintf ("Error: Missing opening end token after <<\n");
65 return NULL;
66 }
67 if (strcmp (endtoken, "EOF") != 0) {
68 // In case there will be strings containing "EOF" inside of them, this requirement
69 // can be weakened to only apply for strings which do not contain "EOF".
70 eprintf ("Error: End token must be \"EOF\", got \"%s\" instead.", endtoken);
71 return NULL;
72 }
73 RStrBuf *buf = r_strbuf_new ("");
74 char *line = *nextline;
75 size_t linesz = 0;
76 do {
77 *nextline = readline (line, &linesz);
78 (*linenum)++;
79 char *end = strstr (line, endtoken);
80 if (end != line) {
81 // Require the EOF to be at the beginning of the line.
82 // This means makes it impossible to write multiline tests without a trailing newline.
83 // This requirement could be lifted later if necessary.
84 end = NULL;
85 }
86 if (end) {
87 *end = '\0';
88 }
89 r_strbuf_append (buf, line);
90 if (end) {
91 return r_strbuf_drain (buf);
92 } else {
93 r_strbuf_append (buf, "\n");
94 }
95 } while ((line = *nextline));
96 eprintf ("Error: Missing closing end token %s\n", endtoken);
97 r_strbuf_free (buf);
98 return NULL;
99 }
100
101 return strdup (val);
102 }
103
r2r_load_cmd_test_file(const char * file)104 R_API RPVector *r2r_load_cmd_test_file(const char *file) {
105 char *contents = r_file_slurp (file, NULL);
106 if (!contents) {
107 eprintf ("Failed to open file \"%s\"\n", file);
108 return NULL;
109 }
110
111 RPVector *ret = r_pvector_new (NULL);
112 if (!ret) {
113 free (contents);
114 return NULL;
115 }
116 R2RCmdTest *test = r2r_cmd_test_new ();
117 if (!test) {
118 free (contents);
119 r_pvector_free (ret);
120 return NULL;
121 }
122
123 ut64 linenum = 0;
124 char *line = contents;
125 size_t linesz;
126 char *nextline;
127 do {
128 nextline = readline (line, &linesz);
129 linenum++;
130 if (!linesz) {
131 continue;
132 }
133 if (*line == '#') {
134 continue;
135 }
136 char *val = strchr (line, '=');
137 if (val) {
138 *val = '\0';
139 val++;
140 }
141
142 // RUN is the only cmd without value
143 if (strcmp (line, "RUN") == 0) {
144 test->run_line = linenum;
145 if (!test->cmds.value) {
146 eprintf (LINEFMT "Error: Test without CMDS key\n", file, linenum);
147 goto fail;
148 }
149 if (!(test->expect.value || test->expect_err.value)) {
150 if (!(test->regexp_out.value || test->regexp_err.value)) {
151 eprintf (LINEFMT "Error: Test without EXPECT or EXPECT_ERR key"
152 " (did you forget an EOF?)\n", file, linenum);
153 goto fail;
154 }
155 }
156 r_pvector_push (ret, test);
157 test = r2r_cmd_test_new ();
158 if (!test) {
159 goto beach;
160 }
161 continue;
162 }
163
164 #define DO_KEY_STR(key, field) \
165 if (strcmp (line, key) == 0) { \
166 if (test->field.value) { \
167 free (test->field.value); \
168 eprintf (LINEFMT "Warning: Duplicate key \"%s\"\n", file, linenum, key); \
169 } \
170 if (!val) { \
171 eprintf (LINEFMT "Error: No value for key \"%s\"\n", file, linenum, key); \
172 goto fail; \
173 } \
174 test->field.line_begin = linenum; \
175 test->field.value = read_string_val (&nextline, val, &linenum); \
176 test->field.line_end = linenum + 1; \
177 if (!test->field.value) { \
178 eprintf (LINEFMT "Error: Failed to read value for key \"%s\"\n", file, linenum, key); \
179 goto fail; \
180 } \
181 continue; \
182 }
183
184 #define DO_KEY_BOOL(key, field) \
185 if (strcmp (line, key) == 0) { \
186 if (test->field.value) { \
187 eprintf (LINEFMT "Warning: Duplicate key \"%s\"\n", file, linenum, key); \
188 } \
189 test->field.set = true; \
190 /* Strip comment */ \
191 char *cmt = strchr (val, '#'); \
192 if (cmt) { \
193 *cmt = '\0'; \
194 cmt--; \
195 while (cmt > val && *cmt == ' ') { \
196 *cmt = '\0'; \
197 cmt--; \
198 } \
199 } \
200 if (!strcmp (val, "1")) { \
201 test->field.value = true; \
202 } else if (!strcmp (val, "0")) { \
203 test->field.value = false; \
204 } else { \
205 eprintf (LINEFMT "Error: Invalid value \"%s\" for boolean key \"%s\", only \"1\" or \"0\" allowed.\n", file, linenum, val, key); \
206 goto fail; \
207 } \
208 continue; \
209 }
210
211 #define DO_KEY_NUM(key, field) \
212 if (strcmp (line, key) == 0) { \
213 if (test->field.value) { \
214 eprintf (LINEFMT "Warning: Duplicate key \"%s\"\n", file, linenum, key); \
215 } \
216 test->field.set = true; \
217 /* Strip comment */ \
218 char *cmt = strchr (val, '#'); \
219 if (cmt) { \
220 *cmt = '\0'; \
221 cmt--; \
222 while (cmt > val && *cmt == ' ') { \
223 *cmt = '\0'; \
224 cmt--; \
225 } \
226 } \
227 char *endval; \
228 test->field.value = strtol (val, &endval, 0); \
229 if (!endval || *endval) { \
230 eprintf (LINEFMT "Error: Invalid value \"%s\" for numeric key \"%s\", only numbers allowed.\n", file, linenum, val, key); \
231 goto fail; \
232 } \
233 continue; \
234 }
235
236 R2R_CMD_TEST_FOREACH_RECORD(DO_KEY_STR, DO_KEY_BOOL, DO_KEY_NUM)
237 #undef DO_KEY_STR
238 #undef DO_KEY_BOOL
239 #undef DO_KEY_NUM
240
241 eprintf (LINEFMT "Unknown key \"%s\".\n", file, linenum, line);
242 } while ((line = nextline));
243 beach:
244 free (contents);
245
246 if (test && (test->name.value || test->cmds.value || test->expect.value)) {
247 eprintf ("Warning: found test tokens at the end of \"%s\" without RUN.\n", file);
248 }
249 r2r_cmd_test_free (test);
250 return ret;
251 fail:
252 r2r_cmd_test_free (test);
253 test = NULL;
254 r_pvector_free (ret);
255 ret = NULL;
256 goto beach;
257 }
258
r2r_asm_test_new(void)259 R_API R2RAsmTest *r2r_asm_test_new(void) {
260 return R_NEW0 (R2RAsmTest);
261 }
262
r2r_asm_test_free(R2RAsmTest * test)263 R_API void r2r_asm_test_free(R2RAsmTest *test) {
264 if (!test) {
265 return;
266 }
267 free (test->disasm);
268 free (test->bytes);
269 free (test);
270 }
271
parse_asm_path(const char * path,RStrConstPool * strpool,const char ** arch_out,const char ** cpuout,int * bitsout)272 static bool parse_asm_path(const char *path, RStrConstPool *strpool, const char **arch_out, const char **cpuout, int *bitsout) {
273 RList *file_tokens = r_str_split_duplist (path, R_SYS_DIR, true);
274 if (r_list_empty (file_tokens)) {
275 r_list_free (file_tokens);
276 return false;
277 }
278
279 // Possibilities:
280 // arm
281 // arm_32
282 // arm_cortex_32
283
284 char *arch = r_list_last (file_tokens);
285 if (!*arch) {
286 r_list_free (file_tokens);
287 return false;
288 }
289 char *second = strchr (arch, '_');
290 if (second) {
291 *second = '\0';
292 second++;
293 char *third = strchr (second, '_');
294 if (third) {
295 *third = '\0';
296 third++;
297 *cpuout = r_str_constpool_get (strpool, second);
298 *bitsout = atoi (third);
299 } else {
300 *cpuout = NULL;
301 *bitsout = atoi (second);
302 }
303 } else {
304 *cpuout = NULL;
305 *bitsout = 0;
306 }
307 *arch_out = r_str_constpool_get (strpool, arch);
308 r_list_free (file_tokens);
309 return true;
310 }
311
r2r_load_asm_test_file(RStrConstPool * strpool,const char * file)312 R_API RPVector *r2r_load_asm_test_file(RStrConstPool *strpool, const char *file) {
313 const char *arch;
314 const char *cpu;
315 int bits;
316 if (!parse_asm_path (file, strpool, &arch, &cpu, &bits)) {
317 eprintf ("Failed to parse arch/cpu/bits from path %s\n", file);
318 return NULL;
319 }
320
321 char *contents = r_file_slurp (file, NULL);
322 if (!contents) {
323 eprintf ("Failed to open file \"%s\"\n", file);
324 return NULL;
325 }
326
327 RPVector *ret = r_pvector_new (NULL);
328 if (!ret) {
329 return NULL;
330 }
331
332 ut64 linenum = 0;
333 char *line = contents;
334 size_t linesz;
335 char *nextline;
336 do {
337 nextline = readline (line, &linesz);
338 linenum++;
339 if (!linesz) {
340 continue;
341 }
342 if (*line == '#') {
343 continue;
344 }
345
346 int mode = 0;
347 while (*line && *line != ' ') {
348 switch (*line) {
349 case 'a':
350 mode |= R2R_ASM_TEST_MODE_ASSEMBLE;
351 break;
352 case 'd':
353 mode |= R2R_ASM_TEST_MODE_DISASSEMBLE;
354 break;
355 case 'E':
356 mode |= R2R_ASM_TEST_MODE_BIG_ENDIAN;
357 break;
358 case 'B':
359 mode |= R2R_ASM_TEST_MODE_BROKEN;
360 break;
361 default:
362 eprintf (LINEFMT "Warning: Invalid mode char '%c'\n", file, linenum, *line);
363 goto fail;
364 }
365 line++;
366 }
367 if (!(mode & R2R_ASM_TEST_MODE_ASSEMBLE) && !(mode & R2R_ASM_TEST_MODE_DISASSEMBLE)) {
368 eprintf (LINEFMT "Warning: Mode specifies neither assemble nor disassemble.\n", file, linenum);
369 continue;
370 }
371
372 char *disasm = strchr (line, '"');
373 if (!disasm) {
374 eprintf (LINEFMT "Error: Expected \" to begin disassembly.\n", file, linenum);
375 goto fail;
376 }
377 disasm++;
378 char *hex = strchr (disasm, '"');
379 if (!hex) {
380 eprintf (LINEFMT "Error: Expected \" to end disassembly.\n", file, linenum);
381 goto fail;
382 }
383 *hex = '\0';
384 hex++;
385 r_str_trim (disasm);
386
387 while (*hex && *hex == ' ') {
388 hex++;
389 }
390
391 char *offset = strchr (hex, ' ');
392 if (offset) {
393 *offset = '\0';
394 offset++;
395 }
396
397 size_t hexlen = strlen (hex);
398 if (!hexlen) {
399 eprintf (LINEFMT "Error: Expected hex chars.\n", file, linenum);
400 goto fail;
401 }
402 ut8 *bytes = malloc (hexlen);
403 if (!bytes) {
404 break;
405 }
406 int bytesz = r_hex_str2bin (hex, bytes);
407 if (bytesz == 0) {
408 eprintf (LINEFMT "Error: Expected hex chars.\n", file, linenum);
409 goto fail;
410 }
411 if (bytesz < 0) {
412 eprintf (LINEFMT "Error: Odd number of hex chars: %s\n", file, linenum, hex);
413 goto fail;
414 }
415
416 R2RAsmTest *test = r2r_asm_test_new ();
417 if (!test) {
418 free (bytes);
419 goto fail;
420 }
421 test->line = linenum;
422 test->bits = bits;
423 test->arch = arch;
424 test->cpu = cpu;
425 test->mode = mode;
426 test->offset = offset ? (ut64)strtoull (offset, NULL, 0) : 0;
427 test->disasm = strdup (disasm);
428 test->bytes = bytes;
429 test->bytes_size = (size_t)bytesz;
430 r_pvector_push (ret, test);
431 } while ((line = nextline));
432
433 beach:
434 free (contents);
435 return ret;
436 fail:
437 r_pvector_free (ret);
438 ret = NULL;
439 goto beach;
440 }
441
r2r_json_test_new(void)442 R_API R2RJsonTest *r2r_json_test_new(void) {
443 return R_NEW0 (R2RJsonTest);
444 }
445
r2r_json_test_free(R2RJsonTest * test)446 R_API void r2r_json_test_free(R2RJsonTest *test) {
447 if (!test) {
448 return;
449 }
450 free (test->cmd);
451 free (test);
452 }
453
r2r_load_json_test_file(const char * file)454 R_API RPVector *r2r_load_json_test_file(const char *file) {
455 char *contents = r_file_slurp (file, NULL);
456 if (!contents) {
457 eprintf ("Failed to open file \"%s\"\n", file);
458 return NULL;
459 }
460
461 RPVector *ret = r_pvector_new (NULL);
462 if (!ret) {
463 free (contents);
464 return NULL;
465 }
466
467 ut64 linenum = 0;
468 char *line = contents;
469 size_t linesz;
470 char *nextline;
471 do {
472 nextline = readline (line, &linesz);
473 linenum++;
474 if (!linesz) {
475 continue;
476 }
477 if (*line == '#') {
478 continue;
479 }
480
481 char *broken_token = strstr (line, "BROKEN");
482 if (broken_token) {
483 *broken_token = '\0';
484 }
485
486 r_str_trim (line);
487 if (!*line) {
488 // empty line
489 continue;
490 }
491
492 R2RJsonTest *test = r2r_json_test_new ();
493 if (!test) {
494 break;
495 }
496 test->line = linenum;
497 test->cmd = strdup (line);
498 if (!test->cmd) {
499 r2r_json_test_free (test);
500 break;
501 }
502 test->broken = broken_token ? true : false;
503 r_pvector_push (ret, test);
504 } while ((line = nextline));
505
506 free (contents);
507 return ret;
508 }
509
r2r_fuzz_test_free(R2RFuzzTest * test)510 R_API void r2r_fuzz_test_free(R2RFuzzTest *test) {
511 if (!test) {
512 return;
513 }
514 free (test->file);
515 free (test);
516 }
517
r2r_test_free(R2RTest * test)518 R_API void r2r_test_free(R2RTest *test) {
519 if (!test) {
520 return;
521 }
522 switch (test->type) {
523 case R2R_TEST_TYPE_CMD:
524 r2r_cmd_test_free (test->cmd_test);
525 break;
526 case R2R_TEST_TYPE_ASM:
527 r2r_asm_test_free (test->asm_test);
528 break;
529 case R2R_TEST_TYPE_JSON:
530 r2r_json_test_free (test->json_test);
531 break;
532 case R2R_TEST_TYPE_FUZZ:
533 r2r_fuzz_test_free (test->fuzz_test);
534 break;
535 }
536 free (test);
537 }
538
r2r_test_database_new(void)539 R_API R2RTestDatabase *r2r_test_database_new(void) {
540 R2RTestDatabase *db = R_NEW (R2RTestDatabase);
541 if (!db) {
542 return NULL;
543 }
544 r_pvector_init (&db->tests, (RPVectorFree)r2r_test_free);
545 r_str_constpool_init (&db->strpool);
546 return db;
547 }
548
r2r_test_database_free(R2RTestDatabase * db)549 R_API void r2r_test_database_free(R2RTestDatabase *db) {
550 if (!db) {
551 return;
552 }
553 r_pvector_clear (&db->tests);
554 r_str_constpool_fini (&db->strpool);
555 free (db);
556 }
557
test_type_for_path(const char * path,bool * load_plugins)558 static R2RTestType test_type_for_path(const char *path, bool *load_plugins) {
559 R2RTestType ret = R2R_TEST_TYPE_CMD;
560 char *pathdup = strdup (path);
561 RList *tokens = r_str_split_list (pathdup, R_SYS_DIR, 0);
562 if (!tokens) {
563 return ret;
564 }
565 if (!r_list_empty (tokens)) {
566 r_list_pop (tokens);
567 }
568 RListIter *it;
569 char *token;
570 *load_plugins = false;
571 r_list_foreach (tokens, it, token) {
572 if (!strcmp (token, "asm")) {
573 ret = R2R_TEST_TYPE_ASM;
574 continue;
575 }
576 if (!strcmp (token, "json")) {
577 ret = R2R_TEST_TYPE_JSON;
578 continue;
579 }
580 if (!strcmp (token, "extras")) {
581 *load_plugins = true;
582 }
583 }
584 r_list_free (tokens);
585 free (pathdup);
586 return ret;
587 }
588
database_load(R2RTestDatabase * db,const char * path,int depth)589 static bool database_load(R2RTestDatabase *db, const char *path, int depth) {
590 if (depth <= 0) {
591 eprintf ("Directories for loading tests too deep: %s\n", path);
592 return false;
593 }
594 if (r_file_is_directory (path)) {
595 RList *dir = r_sys_dir (path);
596 if (!dir) {
597 return false;
598 }
599 RListIter *it;
600 const char *subname;
601 RStrBuf subpath;
602 r_strbuf_init (&subpath);
603 char *sa = r_sys_getenv ("R2R_SKIP_ARCHOS");
604 bool skip_archos = (sa && !strcmp (sa, "1"));
605 free (sa);
606 bool ret = true;
607 r_list_foreach (dir, it, subname) {
608 if (*subname == '.') {
609 continue;
610 }
611 if (!strcmp (subname, "extras")) {
612 // Only load "extras" dirs if explicitly specified
613 eprintf ("Skipping %s"R_SYS_DIR"%s because it requires additional dependencies.\n", path, subname);
614 continue;
615 }
616 bool is_archos_folder = !strcmp (path, "archos") || r_str_endswith (path, R_SYS_DIR"archos");
617 if (is_archos_folder && (skip_archos || strcmp (subname, R2R_ARCH_OS))) {
618 eprintf ("Skipping %s"R_SYS_DIR"%s because it does not match the current platform.\n", path, subname);
619 continue;
620 }
621 r_strbuf_setf (&subpath, "%s%s%s", path, R_SYS_DIR, subname);
622 if (!database_load (db, r_strbuf_get (&subpath), depth - 1)) {
623 ret = false;
624 break;
625 }
626 }
627 r_strbuf_fini (&subpath);
628 r_list_free (dir);
629 return ret;
630 }
631
632 if (!r_file_exists (path)) {
633 eprintf ("Path \"%s\" does not exist\n", path);
634 return false;
635 }
636
637 // Not a directory but exists, load a file
638 const char *pooled_path = r_str_constpool_get (&db->strpool, path);
639 bool load_plugins = false;
640 R2RTestType test_type = test_type_for_path (path, &load_plugins);
641 switch (test_type) {
642 case R2R_TEST_TYPE_CMD: {
643 RPVector *cmd_tests = r2r_load_cmd_test_file (path);
644 if (!cmd_tests) {
645 return false;
646 }
647 void **it;
648 r_pvector_foreach (cmd_tests, it) {
649 R2RTest *test = R_NEW (R2RTest);
650 if (!test) {
651 continue;
652 }
653 test->type = R2R_TEST_TYPE_CMD;
654 test->path = pooled_path;
655 test->cmd_test = *it;
656 test->cmd_test->load_plugins = load_plugins;
657 r_pvector_push (&db->tests, test);
658 }
659 r_pvector_free (cmd_tests);
660 break;
661 }
662 case R2R_TEST_TYPE_ASM: {
663 RPVector *asm_tests = r2r_load_asm_test_file (&db->strpool, path);
664 if (!asm_tests) {
665 return false;
666 }
667 void **it;
668 r_pvector_foreach (asm_tests, it) {
669 R2RTest *test = R_NEW (R2RTest);
670 if (!test) {
671 continue;
672 }
673 test->type = R2R_TEST_TYPE_ASM;
674 test->path = pooled_path;
675 test->asm_test = *it;
676 r_pvector_push (&db->tests, test);
677 }
678 r_pvector_free (asm_tests);
679 break;
680 }
681 case R2R_TEST_TYPE_JSON: {
682 RPVector *json_tests = r2r_load_json_test_file (path);
683 if (!json_tests) {
684 return false;
685 }
686 void **it;
687 r_pvector_foreach (json_tests, it) {
688 R2RTest *test = R_NEW (R2RTest);
689 if (!test) {
690 continue;
691 }
692 test->type = R2R_TEST_TYPE_JSON;
693 test->path = pooled_path;
694 test->json_test = *it;
695 test->json_test->load_plugins = load_plugins;
696 r_pvector_push (&db->tests, test);
697 }
698 r_pvector_free (json_tests);
699 break;
700 }
701 case R2R_TEST_TYPE_FUZZ:
702 // shouldn't come here, fuzz tests are loaded differently
703 break;
704 }
705
706 return true;
707 }
708
r2r_test_database_load(R2RTestDatabase * db,const char * path)709 R_API bool r2r_test_database_load(R2RTestDatabase *db, const char *path) {
710 return database_load (db, path, 4);
711 }
712
database_load_fuzz_file(R2RTestDatabase * db,const char * path,const char * file)713 static void database_load_fuzz_file(R2RTestDatabase *db, const char *path, const char *file) {
714 R2RFuzzTest *fuzz_test = R_NEW (R2RFuzzTest);
715 if (!fuzz_test) {
716 return;
717 }
718 fuzz_test->file = strdup (file);
719 if (!fuzz_test->file) {
720 free (fuzz_test);
721 return;
722 }
723 R2RTest *test = R_NEW (R2RTest);
724 if (!test) {
725 free (fuzz_test->file);
726 free (fuzz_test);
727 return;
728 }
729 test->type = R2R_TEST_TYPE_FUZZ;
730 test->fuzz_test = fuzz_test;
731 test->path = r_str_constpool_get (&db->strpool, path);
732 r_pvector_push (&db->tests, test);
733 }
734
r2r_test_database_load_fuzz(R2RTestDatabase * db,const char * path)735 R_API bool r2r_test_database_load_fuzz(R2RTestDatabase *db, const char *path) {
736 if (r_file_is_directory (path)) {
737 RList *dir = r_sys_dir (path);
738 if (!dir) {
739 return false;
740 }
741 RListIter *it;
742 const char *subname;
743 RStrBuf subpath;
744 r_strbuf_init (&subpath);
745 bool ret = true;
746 r_list_foreach (dir, it, subname) {
747 if (*subname == '.') {
748 continue;
749 }
750 r_strbuf_setf (&subpath, "%s%s%s", path, R_SYS_DIR, subname);
751 if (r_file_is_directory (r_strbuf_get (&subpath))) {
752 // only load 1 level deep
753 continue;
754 }
755 database_load_fuzz_file (db, path, r_strbuf_get (&subpath));
756 }
757 r_strbuf_fini (&subpath);
758 r_list_free (dir);
759 return ret;
760 }
761
762 if (!r_file_exists (path)) {
763 eprintf ("Path \"%s\" does not exist\n", path);
764 return false;
765 }
766
767 // Just a single file
768 database_load_fuzz_file (db, path, path);
769 return true;
770 }
771