Add support for GPG signature enforcement on booted
[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, 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         device_handler_add_device(test->handler, ctx->device);
97
98         return ctx;
99 }
100
101 /* define our own test platform */
102 static bool test_platform_probe(struct platform *p __attribute__((unused)),
103                 void *ctx __attribute__((unused)))
104 {
105         return true;
106 }
107
108 struct platform test_platform = {
109         .name = "test",
110         .probe = test_platform_probe,
111 };
112
113 register_platform(test_platform);
114
115 struct parser_test *test_init(void)
116 {
117         struct parser_test *test;
118
119         test = talloc_zero(NULL, struct parser_test);
120         platform_init(NULL);
121         test->handler = device_handler_init(NULL, NULL, 0);
122         test->ctx = test_create_context(test);
123         list_init(&test->files);
124
125         return test;
126 }
127
128 void test_fini(struct parser_test *test)
129 {
130         device_handler_destroy(test->handler);
131         talloc_free(test);
132         platform_fini();
133 }
134
135 void __test_read_conf_data(struct parser_test *test,
136                 struct discover_device *dev, const char *conf_file,
137                 const char *buf, size_t len)
138 {
139         test_add_file_data(test, dev, conf_file, buf, len);
140 }
141
142 void test_read_conf_file(struct parser_test *test, const char *filename,
143                 const char *conf_file)
144 {
145         struct stat stat;
146         size_t size;
147         char *path;
148         int fd, rc;
149         char *buf;
150
151         path = talloc_asprintf(test, "%s/%s", TEST_CONF_BASE, filename);
152
153         fd = open(path, O_RDONLY);
154         if (fd < 0)
155                 err(EXIT_FAILURE, "Can't open test conf file %s\n", path);
156
157         rc = fstat(fd, &stat);
158         assert(!rc);
159         (void)rc;
160
161         size = stat.st_size;
162         buf = talloc_array(test, char, size + 1);
163
164         rc = read(fd, buf, size);
165         assert(rc == (ssize_t)size);
166
167         *(buf + size) = '\0';
168
169         close(fd);
170         talloc_free(path);
171
172         test_add_file_data(test, test->ctx->device, conf_file, buf, size);
173 }
174
175 void test_add_file_data(struct parser_test *test, struct discover_device *dev,
176                 const char *filename, const void *data, int size)
177 {
178         struct test_file *file;
179
180         file = talloc_zero(test, struct test_file);
181         file->type = TEST_FILE;
182         file->dev = dev;
183         file->name = filename;
184         file->data = talloc_memdup(test, data, size);
185         file->size = size;
186         list_add(&test->files, &file->list);
187 }
188
189 void test_add_dir(struct parser_test *test, struct discover_device *dev,
190                 const char *dirname)
191 {
192         struct test_file *file;
193
194         file = talloc_zero(test, struct test_file);
195         file->type = TEST_DIR;
196         file->dev = dev;
197         file->name = dirname;
198         /* Pick a non-zero size for directories so that "[ -s <dir
199          * path> ]" sees that the file has non-zero size. */
200         file->size = 1;
201         list_add(&test->files, &file->list);
202 }
203
204 void test_set_event_source(struct parser_test *test)
205 {
206         test->ctx->event = talloc_zero(test->ctx, struct event);
207 }
208
209 void test_set_event_param(struct event *event, const char *name,
210                 const char *value)
211 {
212         event_set_param(event, name, value);
213 }
214
215 void test_set_event_device(struct event *event, const char *dev)
216 {
217         event->device = talloc_strdup(event, dev);
218 }
219
220 int parser_request_file(struct discover_context *ctx,
221                 struct discover_device *dev, const char *filename,
222                 char **buf, int *len)
223 {
224         struct parser_test *test = ctx->test_data;
225         struct test_file *file;
226         char *tmp;
227
228         list_for_each_entry(&test->files, file, list) {
229                 if (file->dev != dev)
230                         continue;
231                 if (strcmp(file->name, filename))
232                         continue;
233                 if (file->type != TEST_FILE)
234                         continue;
235
236                 /* the read_file() interface always adds a trailing null
237                  * for string-safety; do the same here */
238                 tmp = talloc_array(test, char, file->size + 1);
239                 memcpy(tmp, file->data, file->size);
240                 tmp[file->size] = '\0';
241                 *buf = tmp;
242                 *len = file->size;
243                 return 0;
244         }
245
246         return -1;
247 }
248
249 int parser_stat_path(struct discover_context *ctx,
250                 struct discover_device *dev, const char *path,
251                 struct stat *statbuf)
252 {
253         struct parser_test *test = ctx->test_data;
254         struct test_file *file;
255
256         list_for_each_entry(&test->files, file, list) {
257                 if (file->dev != dev)
258                         continue;
259                 if (strcmp(file->name, path))
260                         continue;
261
262                 statbuf->st_size = (off_t)file->size;
263                 switch (file->type) {
264                 case TEST_FILE:
265                         statbuf->st_mode = S_IFREG;
266                         break;
267                 case TEST_DIR:
268                         statbuf->st_mode = S_IFDIR;
269                         break;
270                 default:
271                         fprintf(stderr, "%s: bad test file mode %d!", __func__,
272                                 file->type);
273                         exit(EXIT_FAILURE);
274                 }
275
276                 return 0;
277         }
278
279         return -1;
280 }
281
282 int parser_replace_file(struct discover_context *ctx,
283                 struct discover_device *dev, const char *filename,
284                 char *buf, int len)
285 {
286         struct parser_test *test = ctx->test_data;
287         struct test_file *f, *file;
288
289         list_for_each_entry(&test->files, f, list) {
290                 if (f->dev != dev)
291                         continue;
292                 if (strcmp(f->name, filename))
293                         continue;
294
295                 file = f;
296                 break;
297         }
298
299         if (!file) {
300                 file = talloc_zero(test, struct test_file);
301                 file->dev = dev;
302                 file->name = filename;
303                 list_add(&test->files, &file->list);
304         }
305
306         file->data = talloc_memdup(test, buf, len);
307         file->size = len;
308         return 0;
309 }
310
311 struct load_url_result *load_url_async(void *ctx, struct pb_url *url,
312                 load_url_complete async_cb, void *async_data)
313 {
314         struct conf_context *conf = async_data;
315         struct parser_test *test = conf->dc->test_data;
316         struct load_url_result *result;
317         char tmp[] = "/tmp/pb-XXXXXX";
318         ssize_t rc = -1, sz = 0;
319         struct test_file *file;
320         int fd;
321
322         fd = mkstemp(tmp);
323
324         if (fd < 0)
325                 return NULL;
326
327         /* Some parsers will expect to need to read a file, so write the
328          * specified file to a temporary file */
329         list_for_each_entry(&test->files, file, list) {
330                 if (file->dev)
331                         continue;
332
333                 if (strcmp(file->name, url->full))
334                         continue;
335
336                 while (sz < file->size) {
337                         rc = write(fd, file->data, file->size);
338                         if (rc < 0) {
339                                 fprintf(stderr,
340                                         "Failed to write to tmpfile, %m\n");
341                                 break;
342                         }
343                         sz += rc;
344                 }
345                 break;
346         }
347
348         close(fd);
349
350         result = talloc_zero(ctx, struct load_url_result);
351         if (!result)
352                 return NULL;
353
354         result->local = talloc_strdup(result, tmp);
355         if (rc < 0)
356                 result->status = LOAD_ERROR;
357         else
358                 result->status = result->local ? LOAD_OK : LOAD_ERROR;
359         result->cleanup_local = true;
360
361         async_cb(result, conf);
362
363         return result;
364 }
365
366 int parser_request_url(struct discover_context *ctx, struct pb_url *url,
367                 char **buf, int *len)
368 {
369         struct parser_test *test = ctx->test_data;
370         struct test_file *file;
371         char *tmp;
372
373         list_for_each_entry(&test->files, file, list) {
374                 if (file->dev)
375                         continue;
376
377                 if (strcmp(file->name, url->full))
378                         continue;
379
380                 /* the read_file() interface always adds a trailing null
381                  * for string-safety; do the same here */
382                 tmp = talloc_array(test, char, file->size + 1);
383                 memcpy(tmp, file->data, file->size);
384                 tmp[file->size] = '\0';
385                 *buf = tmp;
386                 *len = file->size;
387                 return 0;
388         }
389
390         return -1;
391 }
392
393 int test_run_parser(struct parser_test *test, const char *parser_name)
394 {
395         struct p_item* i;
396
397         list_for_each_entry(&parsers, i, list) {
398                 if (strcmp(i->parser->name, parser_name))
399                         continue;
400                 test->ctx->parser = i->parser;
401                 return i->parser->parse(test->ctx);
402         }
403
404         errx(EXIT_FAILURE, "%s: parser '%s' not found", __func__, parser_name);
405 }
406
407 bool resource_resolve(struct device_handler *handler, struct parser *parser,
408                 struct resource *resource)
409 {
410         if (!resource)
411                 return true;
412         if (resource->resolved)
413                 return true;
414
415         assert(parser);
416         assert(parser->resolve_resource);
417
418         return parser->resolve_resource(handler, resource);
419 }
420
421 void boot_option_resolve(struct device_handler *handler,
422                 struct discover_boot_option *opt)
423 {
424         resource_resolve(handler, opt->source, opt->boot_image);
425         resource_resolve(handler, opt->source, opt->initrd);
426         resource_resolve(handler, opt->source, opt->icon);
427 }
428
429 void test_hotplug_device(struct parser_test *test, struct discover_device *dev)
430 {
431         struct discover_boot_option *opt;
432
433         device_handler_add_device(test->handler, dev);
434
435         list_for_each_entry(&test->ctx->boot_options, opt, list)
436                 boot_option_resolve(test->handler, opt);
437 }
438
439 void test_remove_device(struct parser_test *test, struct discover_device *dev)
440 {
441         struct discover_boot_option *opt, *tmp;
442
443         if (dev == test->ctx->device) {
444                 list_for_each_entry_safe(&test->ctx->boot_options,
445                                 opt, tmp, list) {
446                         list_remove(&opt->list);
447                         talloc_free(opt);
448                 }
449         }
450
451         device_handler_remove(test->handler, dev);
452 }
453
454 struct discover_boot_option *get_boot_option(struct discover_context *ctx,
455                 int idx)
456 {
457         struct discover_boot_option *opt;
458         int i = 0;
459
460         list_for_each_entry(&ctx->boot_options, opt, list) {
461                 if (i++ == idx)
462                         return opt;
463         }
464
465         assert(0);
466
467         return NULL;
468 }
469
470 void __check_boot_option_count(struct discover_context *ctx, int count,
471                 const char *file, int line)
472 {
473         struct discover_boot_option *opt;
474         int defaults = 0, i = 0;
475
476         list_for_each_entry(&ctx->boot_options, opt, list) {
477                 i++;
478                 if (opt->option->is_default)
479                         defaults++;
480         }
481
482         if (defaults > 1) {
483                 fprintf(stderr, "%s:%d: parser returned multiple default "
484                                 "options\n", file, line);
485                 exit(EXIT_FAILURE);
486         }
487
488         if (i == count)
489                 return;
490
491         fprintf(stderr, "%s:%d: boot option count check failed\n", file, line);
492         fprintf(stderr, "expected %d options, got %d:\n", count, i);
493
494         i = 1;
495         list_for_each_entry(&ctx->boot_options, opt, list)
496                 fprintf(stderr, "  %2d: %s [%s]\n", i++, opt->option->name,
497                                 opt->option->id);
498
499         exit(EXIT_FAILURE);
500 }
501
502 void __check_args(struct discover_boot_option *opt, const char *args,
503                 const char *file, int line)
504 {
505         int rc;
506
507         if (!opt->option->boot_args && !args)
508                 return;
509
510         if (!opt->option->boot_args) {
511                 fprintf(stderr, "%s:%d: arg check failed\n", file, line);
512                 fprintf(stderr, "  no arguments parsed\n");
513                 fprintf(stderr, "  expected '%s'\n", args);
514                 exit(EXIT_FAILURE);
515         }
516
517         rc = strcmp(opt->option->boot_args, args);
518         if (rc) {
519                 fprintf(stderr, "%s:%d: arg check failed\n", file, line);
520                 fprintf(stderr, "  got      '%s'\n", opt->option->boot_args);
521                 fprintf(stderr, "  expected '%s'\n", args);
522                 exit(EXIT_FAILURE);
523         }
524 }
525
526 void __check_name(struct discover_boot_option *opt, const char *name,
527                 const char *file, int line)
528 {
529         int rc;
530
531         rc = strcmp(opt->option->name, name);
532         if (rc) {
533                 fprintf(stderr, "%s:%d: name check failed\n", file, line);
534                 fprintf(stderr, "  got      '%s'\n", opt->option->name);
535                 fprintf(stderr, "  expected '%s'\n", name);
536                 exit(EXIT_FAILURE);
537         }
538 }
539
540 void __check_is_default(struct discover_boot_option *opt,
541                 const char *file, int line)
542 {
543         if (opt->option->is_default)
544                 return;
545
546         fprintf(stderr, "%s:%d: default check failed\n", file, line);
547         exit(EXIT_FAILURE);
548 }
549
550 void __check_resolved_local_resource(struct resource *res,
551                 struct discover_device *dev, const char *local_path,
552                 const char *file, int line)
553 {
554         const char *exp_url, *got_url;
555
556         if (!res)
557                 errx(EXIT_FAILURE, "%s:%d: No resource", file, line);
558
559         if (!res->resolved)
560                 errx(EXIT_FAILURE, "%s:%d: Resource is not resolved",
561                                 file, line);
562
563         exp_url = talloc_asprintf(res, "file://%s%s",
564                         dev->mount_path, local_path);
565         got_url = pb_url_to_string(res->url);
566
567         if (strcmp(got_url, exp_url)) {
568                 fprintf(stderr, "%s:%d: Resource mismatch\n", file, line);
569                 fprintf(stderr, "  got      '%s'\n", got_url);
570                 fprintf(stderr, "  expected '%s'\n", exp_url);
571                 exit(EXIT_FAILURE);
572         }
573 }
574
575 void __check_resolved_url_resource(struct resource *res,
576                 const char *url, const char *file, int line)
577 {
578         char *res_url;
579
580         if (!res)
581                 errx(EXIT_FAILURE, "%s:%d: No resource", file, line);
582
583         if (!res->resolved)
584                 errx(EXIT_FAILURE, "%s:%d: Resource is not resolved",
585                                 file, line);
586
587         res_url = pb_url_to_string(res->url);
588         if (strcmp(url, res_url)) {
589                 fprintf(stderr, "%s:%d: Resource mismatch\n", file, line);
590                 fprintf(stderr, "  got      '%s'\n", res_url);
591                 fprintf(stderr, "  expected '%s'\n", url);
592                 exit(EXIT_FAILURE);
593         }
594 }
595 void __check_unresolved_resource(struct resource *res,
596                 const char *file, int line)
597 {
598         if (!res)
599                 errx(EXIT_FAILURE, "%s:%d: No resource", file, line);
600
601         if (res->resolved)
602                 errx(EXIT_FAILURE, "%s:%d: Resource is resolved", file, line);
603 }
604
605 void __check_not_present_resource(struct resource *res,
606                 const char *file, int line)
607 {
608         if (res)
609                 errx(EXIT_FAILURE, "%s:%d: Resource present", file, line);
610 }
611
612 static void dump_file_data(const void *buf, int len)
613 {
614         int i, j, hex_len = strlen("00 ");
615         const int row_len = 16;
616
617         for (i = 0; i < len; i += row_len) {
618                 char hbuf[row_len * hex_len + 1];
619                 char cbuf[row_len + strlen("|") + 1];
620
621                 for (j = 0; (j < row_len) && ((i+j) < len); j++) {
622                         char c = ((const char *)buf)[i + j];
623
624                         snprintf(hbuf + j * hex_len, hex_len + 1, "%02x ", c);
625
626                         if (!isprint(c))
627                                 c = '.';
628
629                         snprintf(cbuf + j, hex_len + 1, "%c", c);
630                 }
631
632                 strcat(cbuf, "|");
633
634                 fprintf(stderr, "%08x  %*s |%s\n", i,
635                                 0 - (int)sizeof(hbuf) + 1, hbuf, cbuf);
636         }
637 }
638
639 void __check_file_contents(struct parser_test *test,
640                 struct discover_device *dev, const char *filename,
641                 const char *buf, int len,
642                 const char *srcfile, int srcline)
643 {
644         struct test_file *f, *file = NULL;
645
646         list_for_each_entry(&test->files, f, list) {
647                 if (f->dev != dev)
648                         continue;
649                 if (strcmp(f->name, filename))
650                         continue;
651
652                 file = f;
653                 break;
654         }
655
656         if (!file)
657                 errx(EXIT_FAILURE, "%s:%d: File '%s' not found",
658                                 srcfile, srcline, filename);
659
660         if (file->size != len || memcmp(file->data, buf, len)) {
661                 fprintf(stderr, "%s:%d: File '%s' data/size mismatch\n",
662                                 srcfile, srcline, filename);
663                 fprintf(stderr, "Expected:\n");
664                 dump_file_data(buf, len);
665                 fprintf(stderr, "Got:\n");
666                 dump_file_data(file->data, file->size);
667                 exit(EXIT_FAILURE);
668         }
669 }