test/parser: Add parser_is_unique
[petitboot] / test / parser / utils.c
1
2 #include <assert.h>
3 #include <err.h>
4 #include <fcntl.h>
5 #include <stdlib.h>
6 #include <string.h>
7 #include <ctype.h>
8 #include <sys/stat.h>
9 #include <sys/types.h>
10
11 #include <talloc/talloc.h>
12 #include <types/types.h>
13 #include <url/url.h>
14
15 #include "device-handler.h"
16 #include "parser.h"
17 #include "resource.h"
18 #include "event.h"
19 #include "platform.h"
20 #include "paths.h"
21 #include "parser-conf.h"
22
23 #include "parser-test.h"
24
25 struct p_item {
26         struct list_item list;
27         struct parser *parser;
28 };
29
30 struct test_file {
31         struct discover_device  *dev;
32         enum {
33                 TEST_FILE,
34                 TEST_DIR,
35         }                       type;
36         const char              *name;
37         void                    *data;
38         int                     size;
39         struct list_item        list;
40 };
41
42 STATIC_LIST(parsers);
43
44 void __register_parser(struct parser *parser)
45 {
46         struct p_item* i = talloc(NULL, struct p_item);
47
48         i->parser = parser;
49         list_add(&parsers, &i->list);
50 }
51
52 static void __attribute__((destructor)) __cleanup_parsers(void)
53 {
54         struct p_item *item, *tmp;
55
56         list_for_each_entry_safe(&parsers, item, tmp, list)
57                 talloc_free(item);
58 }
59
60 static struct discover_device *test_create_device_simple(
61                 struct parser_test *test)
62 {
63         static int dev_idx;
64         char name[10];
65
66         sprintf(name, "__test%d", dev_idx++);
67
68         return test_create_device(test, name);
69 }
70
71 struct discover_device *test_create_device(struct parser_test *test,
72                 const char *name)
73 {
74         struct discover_device *dev;
75
76         dev = discover_device_create(test->handler, NULL, name);
77
78         dev->device->id = talloc_strdup(dev, name);
79         dev->device_path = talloc_asprintf(dev, "/dev/%s", name);
80         dev->mount_path = talloc_asprintf(dev, "/test/mount/%s", name);
81         dev->mounted = true;
82
83         return dev;
84 }
85
86 static struct discover_context *test_create_context(struct parser_test *test)
87 {
88         struct discover_context *ctx;
89
90         ctx = talloc_zero(test, struct discover_context);
91         assert(ctx);
92
93         list_init(&ctx->boot_options);
94         ctx->device = test_create_device_simple(test);
95         ctx->test_data = test;
96         ctx->handler = test->handler;
97         device_handler_add_device(test->handler, ctx->device);
98
99         return ctx;
100 }
101
102 /* define our own test platform */
103 static bool test_platform_probe(struct platform *p __attribute__((unused)),
104                 void *ctx __attribute__((unused)))
105 {
106         return true;
107 }
108
109 struct platform test_platform = {
110         .name = "test",
111         .probe = test_platform_probe,
112 };
113
114 register_platform(test_platform);
115
116 struct parser_test *test_init(void)
117 {
118         struct parser_test *test;
119
120         test = talloc_zero(NULL, struct parser_test);
121         platform_init(NULL);
122         test->handler = device_handler_init(NULL, NULL, 0);
123         test->ctx = test_create_context(test);
124         list_init(&test->files);
125
126         return test;
127 }
128
129 void test_fini(struct parser_test *test)
130 {
131         device_handler_destroy(test->handler);
132         talloc_free(test);
133         platform_fini();
134 }
135
136 void __test_read_conf_data(struct parser_test *test,
137                 struct discover_device *dev, const char *conf_file,
138                 const char *buf, size_t len)
139 {
140         test_add_file_data(test, dev, conf_file, buf, len);
141 }
142
143 void test_read_conf_file(struct parser_test *test, const char *filename,
144                 const char *conf_file)
145 {
146         struct stat stat;
147         size_t size;
148         char *path;
149         int fd, rc;
150         char *buf;
151
152         path = talloc_asprintf(test, "%s/%s", TEST_CONF_BASE, filename);
153
154         fd = open(path, O_RDONLY);
155         if (fd < 0)
156                 err(EXIT_FAILURE, "Can't open test conf file %s\n", path);
157
158         rc = fstat(fd, &stat);
159         assert(!rc);
160         (void)rc;
161
162         size = stat.st_size;
163         buf = talloc_array(test, char, size + 1);
164
165         rc = read(fd, buf, size);
166         assert(rc == (ssize_t)size);
167
168         *(buf + size) = '\0';
169
170         close(fd);
171         talloc_free(path);
172
173         test_add_file_data(test, test->ctx->device, conf_file, buf, size);
174 }
175
176 void test_add_file_data(struct parser_test *test, struct discover_device *dev,
177                 const char *filename, const void *data, int size)
178 {
179         struct test_file *file;
180
181         file = talloc_zero(test, struct test_file);
182         file->type = TEST_FILE;
183         file->dev = dev;
184         file->name = filename;
185         file->data = talloc_memdup(test, data, size);
186         file->size = size;
187         list_add(&test->files, &file->list);
188 }
189
190 void test_add_dir(struct parser_test *test, struct discover_device *dev,
191                 const char *dirname)
192 {
193         struct test_file *file;
194
195         file = talloc_zero(test, struct test_file);
196         file->type = TEST_DIR;
197         file->dev = dev;
198         file->name = dirname;
199         /* Pick a non-zero size for directories so that "[ -s <dir
200          * path> ]" sees that the file has non-zero size. */
201         file->size = 1;
202         list_add(&test->files, &file->list);
203 }
204
205 void test_set_event_source(struct parser_test *test)
206 {
207         test->ctx->event = talloc_zero(test->ctx, struct event);
208 }
209
210 void test_set_event_param(struct event *event, const char *name,
211                 const char *value)
212 {
213         event_set_param(event, name, value);
214 }
215
216 void test_set_event_device(struct event *event, const char *dev)
217 {
218         event->device = talloc_strdup(event, dev);
219 }
220
221 int parser_request_file(struct discover_context *ctx,
222                 struct discover_device *dev, const char *filename,
223                 char **buf, int *len)
224 {
225         struct parser_test *test = ctx->test_data;
226         struct test_file *file;
227         char *tmp;
228
229         list_for_each_entry(&test->files, file, list) {
230                 if (file->dev != dev)
231                         continue;
232                 if (strcmp(file->name, filename))
233                         continue;
234                 if (file->type != TEST_FILE)
235                         continue;
236
237                 /* the read_file() interface always adds a trailing null
238                  * for string-safety; do the same here */
239                 tmp = talloc_array(test, char, file->size + 1);
240                 memcpy(tmp, file->data, file->size);
241                 tmp[file->size] = '\0';
242                 *buf = tmp;
243                 *len = file->size;
244                 return 0;
245         }
246
247         return -1;
248 }
249
250 int parser_stat_path(struct discover_context *ctx,
251                 struct discover_device *dev, const char *path,
252                 struct stat *statbuf)
253 {
254         struct parser_test *test = ctx->test_data;
255         struct test_file *file;
256
257         list_for_each_entry(&test->files, file, list) {
258                 if (file->dev != dev)
259                         continue;
260                 if (strcmp(file->name, path))
261                         continue;
262
263                 statbuf->st_size = (off_t)file->size;
264                 switch (file->type) {
265                 case TEST_FILE:
266                         statbuf->st_mode = S_IFREG;
267                         break;
268                 case TEST_DIR:
269                         statbuf->st_mode = S_IFDIR;
270                         break;
271                 default:
272                         fprintf(stderr, "%s: bad test file mode %d!", __func__,
273                                 file->type);
274                         exit(EXIT_FAILURE);
275                 }
276
277                 return 0;
278         }
279
280         return -1;
281 }
282
283 int parser_replace_file(struct discover_context *ctx,
284                 struct discover_device *dev, const char *filename,
285                 char *buf, int len)
286 {
287         struct parser_test *test = ctx->test_data;
288         struct test_file *f, *file = NULL;
289
290         list_for_each_entry(&test->files, f, list) {
291                 if (f->dev != dev)
292                         continue;
293                 if (strcmp(f->name, filename))
294                         continue;
295
296                 file = f;
297                 break;
298         }
299
300         if (!file) {
301                 file = talloc_zero(test, struct test_file);
302                 file->dev = dev;
303                 file->name = filename;
304                 list_add(&test->files, &file->list);
305         }
306
307         file->data = talloc_memdup(test, buf, len);
308         file->size = len;
309         return 0;
310 }
311
312 int parser_scandir(struct discover_context *ctx, const char *dirname,
313                    struct dirent ***files, int (*filter)(const struct dirent *)
314                    __attribute__((unused)),
315                    int (*comp)(const struct dirent **, const struct dirent **)
316                    __attribute__((unused)))
317 {
318         struct parser_test *test = ctx->test_data;
319         struct test_file *f;
320         char *filename;
321         struct dirent **dirents = NULL, **new_dirents;
322         int n = 0, namelen;
323
324         list_for_each_entry(&test->files, f, list) {
325                 if (f->dev != ctx->device)
326                         continue;
327
328                 if (strlen(f->name) <= strlen(dirname))
329                         continue;
330
331                 filename = strrchr(f->name, '/');
332                 if (!filename)
333                         continue;
334
335                 namelen = strlen(filename);
336
337                 if (strncmp(f->name, dirname, strlen(f->name) - namelen))
338                         continue;
339
340                 if (!dirents) {
341                         dirents = malloc(sizeof(struct dirent *));
342                 } else {
343                         new_dirents = realloc(dirents, sizeof(struct dirent *)
344                                               * (n + 1));
345                         if (!new_dirents)
346                                 goto err_cleanup;
347
348                         dirents = new_dirents;
349                 }
350
351                 dirents[n] = malloc(sizeof(struct dirent) + namelen + 1);
352
353                 if (!dirents[n])
354                         goto err_cleanup;
355
356                 strcpy(dirents[n]->d_name, filename + 1);
357                 n++;
358         }
359
360         *files = dirents;
361
362         return n;
363
364 err_cleanup:
365         do {
366                 free(dirents[n]);
367         } while (n-- > 0);
368
369         free(dirents);
370
371         return -1;
372 }
373
374 bool parser_is_unique(struct discover_context *ctx, struct discover_device *dev,
375         const char *filename, struct list *found_list)
376 {
377         (void)ctx;
378         (void)dev;
379         (void)filename;
380         (void)found_list;
381
382         /* Just let the parser process everything. */
383         return true;
384 }
385
386 struct load_url_result *load_url_async(void *ctx, struct pb_url *url,
387                 load_url_complete async_cb, void *async_data,
388                 waiter_cb stdout_cb, void *stdout_data)
389 {
390         struct conf_context *conf = async_data;
391         struct parser_test *test = conf->dc->test_data;
392         struct load_url_result *result;
393         char tmp[] = "/tmp/pb-XXXXXX";
394         ssize_t rc = -1, sz = 0;
395         struct test_file *file;
396         int fd;
397
398         /* Ignore the stdout callback for tests */
399         (void)stdout_cb;
400         (void)stdout_data;
401
402         fd = mkstemp(tmp);
403
404         if (fd < 0)
405                 return NULL;
406
407         /* Some parsers will expect to need to read a file, so write the
408          * specified file to a temporary file */
409         list_for_each_entry(&test->files, file, list) {
410                 if (file->dev)
411                         continue;
412
413                 if (strcmp(file->name, url->full))
414                         continue;
415
416                 while (sz < file->size) {
417                         rc = write(fd, file->data, file->size);
418                         if (rc < 0) {
419                                 fprintf(stderr,
420                                         "Failed to write to tmpfile, %m\n");
421                                 break;
422                         }
423                         sz += rc;
424                 }
425                 break;
426         }
427
428         close(fd);
429
430         result = talloc_zero(ctx, struct load_url_result);
431         if (!result)
432                 return NULL;
433
434         result->local = talloc_strdup(result, tmp);
435         result->url = url;
436         if (rc < 0)
437                 result->status = LOAD_ERROR;
438         else
439                 result->status = result->local ? LOAD_OK : LOAD_ERROR;
440         result->cleanup_local = true;
441
442         async_cb(result, conf);
443
444         return result;
445 }
446
447 int parser_request_url(struct discover_context *ctx, struct pb_url *url,
448                 char **buf, int *len)
449 {
450         struct parser_test *test = ctx->test_data;
451         struct test_file *file;
452         char *tmp;
453
454         list_for_each_entry(&test->files, file, list) {
455                 if (file->dev)
456                         continue;
457
458                 if (strcmp(file->name, url->full))
459                         continue;
460
461                 /* the read_file() interface always adds a trailing null
462                  * for string-safety; do the same here */
463                 tmp = talloc_array(test, char, file->size + 1);
464                 memcpy(tmp, file->data, file->size);
465                 tmp[file->size] = '\0';
466                 *buf = tmp;
467                 *len = file->size;
468                 return 0;
469         }
470
471         return -1;
472 }
473
474 int test_run_parser(struct parser_test *test, const char *parser_name)
475 {
476         struct p_item* i;
477
478         list_for_each_entry(&parsers, i, list) {
479                 if (strcmp(i->parser->name, parser_name))
480                         continue;
481                 test->ctx->parser = i->parser;
482                 return i->parser->parse(test->ctx);
483         }
484
485         errx(EXIT_FAILURE, "%s: parser '%s' not found", __func__, parser_name);
486 }
487
488 bool resource_resolve(struct device_handler *handler, struct parser *parser,
489                 struct resource *resource)
490 {
491         if (!resource)
492                 return true;
493         if (resource->resolved)
494                 return true;
495
496         assert(parser);
497         assert(parser->resolve_resource);
498
499         return parser->resolve_resource(handler, resource);
500 }
501
502 void boot_option_resolve(struct device_handler *handler,
503                 struct discover_boot_option *opt)
504 {
505         resource_resolve(handler, opt->source, opt->boot_image);
506         resource_resolve(handler, opt->source, opt->initrd);
507         resource_resolve(handler, opt->source, opt->icon);
508 }
509
510 void test_hotplug_device(struct parser_test *test, struct discover_device *dev)
511 {
512         struct discover_boot_option *opt;
513
514         device_handler_add_device(test->handler, dev);
515
516         list_for_each_entry(&test->ctx->boot_options, opt, list)
517                 boot_option_resolve(test->handler, opt);
518 }
519
520 void test_remove_device(struct parser_test *test, struct discover_device *dev)
521 {
522         struct discover_boot_option *opt, *tmp;
523
524         if (dev == test->ctx->device) {
525                 list_for_each_entry_safe(&test->ctx->boot_options,
526                                 opt, tmp, list) {
527                         list_remove(&opt->list);
528                         talloc_free(opt);
529                 }
530         }
531
532         device_handler_remove(test->handler, dev);
533 }
534
535 struct discover_boot_option *get_boot_option(struct discover_context *ctx,
536                 int idx)
537 {
538         struct discover_boot_option *opt;
539         int i = 0;
540
541         list_for_each_entry(&ctx->boot_options, opt, list) {
542                 if (i++ == idx)
543                         return opt;
544         }
545
546         assert(0);
547
548         return NULL;
549 }
550
551 void __check_boot_option_count(struct discover_context *ctx, int count,
552                 const char *file, int line)
553 {
554         struct discover_boot_option *opt;
555         int defaults = 0, i = 0;
556
557         list_for_each_entry(&ctx->boot_options, opt, list) {
558                 i++;
559                 if (opt->option->is_default)
560                         defaults++;
561         }
562
563         if (defaults > 1) {
564                 fprintf(stderr, "%s:%d: parser returned multiple default "
565                                 "options\n", file, line);
566                 exit(EXIT_FAILURE);
567         }
568
569         if (i == count)
570                 return;
571
572         fprintf(stderr, "%s:%d: boot option count check failed\n", file, line);
573         fprintf(stderr, "expected %d options, got %d:\n", count, i);
574
575         i = 1;
576         list_for_each_entry(&ctx->boot_options, opt, list)
577                 fprintf(stderr, "  %2d: %s [%s]\n", i++, opt->option->name,
578                                 opt->option->id);
579
580         exit(EXIT_FAILURE);
581 }
582
583 void __check_args(struct discover_boot_option *opt, const char *args,
584                 const char *file, int line)
585 {
586         int rc;
587
588         if (!opt->option->boot_args && !args)
589                 return;
590
591         if (!opt->option->boot_args) {
592                 fprintf(stderr, "%s:%d: arg check failed\n", file, line);
593                 fprintf(stderr, "  no arguments parsed\n");
594                 fprintf(stderr, "  expected '%s'\n", args);
595                 exit(EXIT_FAILURE);
596         }
597
598         rc = strcmp(opt->option->boot_args, args);
599         if (rc) {
600                 fprintf(stderr, "%s:%d: arg check failed\n", file, line);
601                 fprintf(stderr, "  got      '%s'\n", opt->option->boot_args);
602                 fprintf(stderr, "  expected '%s'\n", args);
603                 exit(EXIT_FAILURE);
604         }
605 }
606
607 void __check_name(struct discover_boot_option *opt, const char *name,
608                 const char *file, int line)
609 {
610         int rc;
611
612         rc = strcmp(opt->option->name, name);
613         if (rc) {
614                 fprintf(stderr, "%s:%d: name check failed\n", file, line);
615                 fprintf(stderr, "  got      '%s'\n", opt->option->name);
616                 fprintf(stderr, "  expected '%s'\n", name);
617                 exit(EXIT_FAILURE);
618         }
619 }
620
621 void __check_is_default(struct discover_boot_option *opt,
622                 const char *file, int line)
623 {
624         if (opt->option->is_default)
625                 return;
626
627         fprintf(stderr, "%s:%d: default check failed\n", file, line);
628         exit(EXIT_FAILURE);
629 }
630
631 void __check_resolved_local_resource(struct resource *res,
632                 struct discover_device *dev, const char *local_path,
633                 const char *file, int line)
634 {
635         const char *exp_url, *got_url;
636
637         if (!res)
638                 errx(EXIT_FAILURE, "%s:%d: No resource", file, line);
639
640         if (!res->resolved)
641                 errx(EXIT_FAILURE, "%s:%d: Resource is not resolved",
642                                 file, line);
643
644         exp_url = talloc_asprintf(res, "file://%s%s",
645                         dev->mount_path, local_path);
646         got_url = pb_url_to_string(res->url);
647
648         if (strcmp(got_url, exp_url)) {
649                 fprintf(stderr, "%s:%d: Resource mismatch\n", file, line);
650                 fprintf(stderr, "  got      '%s'\n", got_url);
651                 fprintf(stderr, "  expected '%s'\n", exp_url);
652                 exit(EXIT_FAILURE);
653         }
654 }
655
656 void __check_resolved_url_resource(struct resource *res,
657                 const char *url, const char *file, int line)
658 {
659         char *res_url;
660
661         if (!res)
662                 errx(EXIT_FAILURE, "%s:%d: No resource", file, line);
663
664         if (!res->resolved)
665                 errx(EXIT_FAILURE, "%s:%d: Resource is not resolved",
666                                 file, line);
667
668         res_url = pb_url_to_string(res->url);
669         if (strcmp(url, res_url)) {
670                 fprintf(stderr, "%s:%d: Resource mismatch\n", file, line);
671                 fprintf(stderr, "  got      '%s'\n", res_url);
672                 fprintf(stderr, "  expected '%s'\n", url);
673                 exit(EXIT_FAILURE);
674         }
675 }
676 void __check_unresolved_resource(struct resource *res,
677                 const char *file, int line)
678 {
679         if (!res)
680                 errx(EXIT_FAILURE, "%s:%d: No resource", file, line);
681
682         if (res->resolved)
683                 errx(EXIT_FAILURE, "%s:%d: Resource is resolved", file, line);
684 }
685
686 void __check_not_present_resource(struct resource *res,
687                 const char *file, int line)
688 {
689         if (res)
690                 errx(EXIT_FAILURE, "%s:%d: Resource present", file, line);
691 }
692
693 static void dump_file_data(const void *buf, int len)
694 {
695         int i, j, hex_len = strlen("00 ");
696         const int row_len = 16;
697
698         for (i = 0; i < len; i += row_len) {
699                 char hbuf[row_len * hex_len + 1];
700                 char cbuf[row_len + strlen("|") + 1];
701
702                 for (j = 0; (j < row_len) && ((i+j) < len); j++) {
703                         char c = ((const char *)buf)[i + j];
704
705                         snprintf(hbuf + j * hex_len, hex_len + 1, "%02x ", c);
706
707                         if (!isprint(c))
708                                 c = '.';
709
710                         snprintf(cbuf + j, hex_len + 1, "%c", c);
711                 }
712
713                 strcat(cbuf, "|");
714
715                 fprintf(stderr, "%08x  %*s |%s\n", i,
716                                 0 - (int)sizeof(hbuf) + 1, hbuf, cbuf);
717         }
718 }
719
720 void __check_file_contents(struct parser_test *test,
721                 struct discover_device *dev, const char *filename,
722                 const char *buf, int len,
723                 const char *srcfile, int srcline)
724 {
725         struct test_file *f, *file = NULL;
726
727         list_for_each_entry(&test->files, f, list) {
728                 if (f->dev != dev)
729                         continue;
730                 if (strcmp(f->name, filename))
731                         continue;
732
733                 file = f;
734                 break;
735         }
736
737         if (!file)
738                 errx(EXIT_FAILURE, "%s:%d: File '%s' not found",
739                                 srcfile, srcline, filename);
740
741         if (file->size != len || memcmp(file->data, buf, len)) {
742                 fprintf(stderr, "%s:%d: File '%s' data/size mismatch\n",
743                                 srcfile, srcline, filename);
744                 fprintf(stderr, "Expected:\n");
745                 dump_file_data(buf, len);
746                 fprintf(stderr, "Got:\n");
747                 dump_file_data(file->data, file->size);
748                 exit(EXIT_FAILURE);
749         }
750 }